- Load required libraries
library(Boruta)
library(clValid)
library(DESeq2)
library(factoextra)
library(gplots)
library(multtest)
library(NbClust)
library(pvclust)
#library(quantro) #This package was used in R-3.6 version
library(RColorBrewer)
library(rgl)
library(Rtsne)
library(scatterplot3d)
library(viridis)
- Run differential expression analysis using DESeq2 (as suggested in DESeq2’s vignette; http://bioconductor.org/packages/devel/bioc/vignettes/DESeq2/inst/doc/DESeq2.html)
#Load raw read counts (determined with DuffyNGS)
#All time points have three replicates but T15 (which has six replicates)
counts.hypoxia<-as.matrix(read.csv("hypoxia_raw_reads.csv",row.names=1))
#Replace few NA values with zeroes (0.049%)
na.hypoxia<-which(is.na(counts.hypoxia))
counts.hypoxia[na.hypoxia]<-0
#Print time points/replicates in the expression matrix
print(colnames(counts.hypoxia))
[1] "T0_A" "T0_B" "T0_C" "T1_A" "T1_B" "T1_C" "T2_A" "T2_B" "T2_C" "T3_A" "T3_B" "T3_C"
[13] "T4_A" "T4_B" "T4_C" "T5_A" "T5_B" "T5_C" "T6_A" "T6_B" "T6_C" "T7_A" "T7_B" "T7_C"
[25] "T8_A" "T8_B" "T8_C" "T9_A" "T9_B" "T9_C" "T10_A" "T10_B" "T10_C" "T11_A" "T11_B" "T11_C"
[37] "T12_A" "T12_B" "T12_C" "T13_A" "T13_B" "T13_C" "T14_A" "T14_B" "T14_C" "T15_A" "T15_B" "T15_C"
[49] "T15_D" "T15_E" "T15_F" "T16_A" "T16_B" "T16_C" "T17_A" "T17_B" "T17_C" "T18_A" "T18_B" "T18_C"
[61] "T19_A" "T19_B" "T19_C" "T20_A" "T20_B" "T20_C" "T21_A" "T21_B" "T21_C" "T22_A" "T22_B" "T22_C"
[73] "T23_A" "T23_B" "T23_C" "T24_A" "T24_B" "T24_C" "T25_A" "T25_B" "T25_C"
#Create list with differentially expressed genes (DEGs) at each time point
de.genes<-list()
#Save names of all DEGs
DEGs.timecourse<-c()
#DESeq2 analysis is performed for each time point with respect to T0
for(i in c(2:16,18:27))
{
#For time points different to T15
if(i!=16)
{
count.tab<-counts.hypoxia[,c(1:3,((3*i)-2):(3*i))]
#Round read counts (determined by DuffyNGS)
count.tab<-round(count.tab)
#Metadata (triplicates)
exp.metadata <- data.frame(condition = factor(rep(c("control", "tpoint"), c(3,3))))
rownames(exp.metadata) <- colnames(count.tab)
dds <- DESeqDataSetFromMatrix(count.tab, exp.metadata, ~condition)
#Remove genes with zero reads in all samples
dds <- dds[ rowSums(counts(dds)) > 1, ]
#Run main function
dds <- DESeq(dds)
#Output
output.deseq <- results(dds)
#Save names of DEGs
degs<-rownames(output.deseq)[which(output.deseq$padj < 0.05 & abs(output.deseq$log2FoldChange)>1)]
de.genes[[i]]<-degs
DEGs.timecourse<-union(DEGs.timecourse,degs)
}
#For T15
else
{
count.tab<-counts.hypoxia[,c(1:3,((3*i)-2):((3*i)+3))]
#Round read counts
count.tab<-round(count.tab)
#Metadata
exp.metadata <- data.frame(condition = factor(rep(c("control", "tpoint"), c(3,6))))
rownames(exp.metadata) <- colnames(count.tab)
dds <- DESeqDataSetFromMatrix(count.tab, exp.metadata, ~condition)
#Remove genes with zero reads in all samples
dds <- dds[ rowSums(counts(dds)) > 1, ]
#Run main function
dds <- DESeq(dds)
#Output
output.deseq <- results(dds)
#Save names of DEGs
degs<-rownames(output.deseq)[which(output.deseq$padj < 0.05 & abs(output.deseq$log2FoldChange)>1)]
de.genes[[i]]<-degs
DEGs.timecourse<-union(DEGs.timecourse,degs)
}
}
2a. Normalized full expression matrix and perform hierarchichal clustering on median profiles
#Normalize read counts using all time points and replicates
count.tab<-round(counts.hypoxia)
#Metadata
exp.metadata <- data.frame(condition = factor(sapply(1:ncol(counts.hypoxia),function(x){strsplit(colnames(counts.hypoxia)[x],split = "_")[[1]][1]})))
rownames(exp.metadata) <- colnames(count.tab)
#Estimate normalized counts with DESeq2 (log transformation and normal distribution)
dds <- DESeqDataSetFromMatrix(count.tab, exp.metadata, ~condition)
converting counts to integer mode
dds <- estimateSizeFactors(dds)
normalized.counts <- assay(normTransform(dds))
#Estimate median counts for each gene per time point
time.points<-paste(unique(exp.metadata$condition),"_",sep="")
median.normalized.counts<-c()
for(z in time.points)
{
col.pos<-grep(z,colnames(normalized.counts))
median.normalized.counts<-cbind(median.normalized.counts,apply(FUN=median,MARGIN=1,normalized.counts[,col.pos]))
}
colnames(median.normalized.counts)<-paste("T",0:25,sep="")
#Hierarchical clustering with bootstrap values
hierarchical.clustering.euclidean<-pvclust(data = median.normalized.counts,method.dist ="euclidean")
Bootstrap (r = 0.5)... Done.
Bootstrap (r = 0.6)... Done.
Bootstrap (r = 0.7)... Done.
Bootstrap (r = 0.8)... Done.
Bootstrap (r = 0.9)... Done.
Bootstrap (r = 1.0)... Done.
Bootstrap (r = 1.1)... Done.
Bootstrap (r = 1.2)... Done.
Bootstrap (r = 1.3)... Done.
Bootstrap (r = 1.4)... Done.
plot(hierarchical.clustering.euclidean, print.pv=TRUE, print.num=FALSE, float=0.01,
col.pv=c(2,3,8), cex.pv=0.8,cex=0.8,main="Fig S1B")

2b. Apply PCA to the full normalized expression matrix
#The average of the two samples from each reactor was used for defining T15
normalized.counts.t15_average<-cbind(normalized.counts[,1:45],rowMeans(normalized.counts[,c(46,49)]),
rowMeans(normalized.counts[,c(47,50)]),
rowMeans(normalized.counts[,c(48,51)]), normalized.counts[,52:81])
colnames(normalized.counts.t15_average)[46:48]<-c("T15_A","T15_B","T15_C")
#Run PCA
pca.input<-t(normalized.counts.t15_average)
pca.output<-prcomp(pca.input)
#Plot 3D PCA (for the final Figure S1A we used the interactive plot3d function)
#clusters.colors<-c("blue","deepskyblue2","orange","darkolivegreen1","green3","red")
#stateLabels=c(rep(1,2),rep(2,6),3,3,4,3,rep(4,4),rep(5,3),rep(3,3),rep(6,4))
#gr <- grid3d('z')
#plot3d(pca.output$x[,1:3],col=rep(clusters.colors[stateLabels],each=3),size = 10)
#axes3d(labels=F,tick=F,box=F)
#text3d(pca.output$x[,1:3]+1,texts=rownames(pca.input),cex=1.1)
pca.plot<-scatterplot3d(x = pca.output$x[,1], y = pca.output$x[,3], z = pca.output$x[,2],
pch = 19,angle = -190,xlab = "PC1",ylab="PC3",zlab="PC2",color=rep(viridis(26),each=3),main="Fig S1A")
legend(pca.plot$xyz.convert(20, -40, 30), pch=19, col=viridis(26), legend = unique(exp.metadata$condition), bty="o", cex=.35)

2c. Apply clustering algorithms (hierarchichal clustering, k-means) to the full normalized expression matrix
#Hierarchical clustering with bootstrap values (all replicates)
hierarchical.clustering.euclidean.reps<-pvclust(data = normalized.counts.t15_average,method.dist ="euclidean")
Bootstrap (r = 0.5)... Done.
Bootstrap (r = 0.6)... Done.
Bootstrap (r = 0.7)... Done.
Bootstrap (r = 0.8)... Done.
Bootstrap (r = 0.9)... Done.
Bootstrap (r = 1.0)... Done.
Bootstrap (r = 1.1)... Done.
Bootstrap (r = 1.2)... Done.
Bootstrap (r = 1.3)... Done.
Bootstrap (r = 1.4)... Done.
plot(hierarchical.clustering.euclidean.reps, print.pv=TRUE, print.num=FALSE, float=0.01,
col.pv=c(2,3,8), cex.pv=0.4,cex=0.5,main="Fig S1C")

#K-means with k=2
k.means.2<-kmeans(t(normalized.counts.t15_average),2,nstart = 25)
#Print one of the two k-means clusters
print(names(which(k.means.2$cluster == 1)))
[1] "T8_A" "T8_B" "T9_B" "T9_C" "T11_A" "T11_B" "T16_B" "T17_A" "T19_A" "T19_B" "T19_C" "T20_A"
[13] "T20_B" "T20_C" "T21_A" "T21_B"
#K-means with k=6
k.means.6<-kmeans(t(normalized.counts.t15_average),6,nstart = 25)
#Print replicates clusters (related to Fig S1D)
print(k.means.6$cluster)
T0_A T0_B T0_C T1_A T1_B T1_C T2_A T2_B T2_C T3_A T3_B T3_C T4_A T4_B T4_C T5_A T5_B
6 6 6 6 6 6 3 3 3 3 3 3 3 3 3 3 6
T5_C T6_A T6_B T6_C T7_A T7_B T7_C T8_A T8_B T8_C T9_A T9_B T9_C T10_A T10_B T10_C T11_A
3 3 3 3 3 3 3 4 4 3 1 4 4 1 1 3 4
T11_B T11_C T12_A T12_B T12_C T13_A T13_B T13_C T14_A T14_B T14_C T15_A T15_B T15_C T16_A T16_B T16_C
4 2 1 1 1 1 1 1 1 1 1 1 2 1 2 4 2
T17_A T17_B T17_C T18_A T18_B T18_C T19_A T19_B T19_C T20_A T20_B T20_C T21_A T21_B T21_C T22_A T22_B
4 2 2 2 2 2 4 4 4 4 4 4 4 4 5 5 5
T22_C T23_A T23_B T23_C T24_A T24_B T24_C T25_A T25_B T25_C
5 5 5 5 5 5 5 5 5 5
#Compute silhouette clustering metric using clValid package
clValid.internal.metrics <- clValid(t(normalized.counts.t15_average), nClust = 2:26,clMethods = c("hierarchical","kmeans","diana","pam","clara"), validation = "internal")
#Silhouette values were extracted using the summary(clValid.internal.metrics) command
silhouette.kmeans<-c(0,0.4706,0.2948,0.3043,0.3666,0.3814,0.3553,0.3432,0.3163,0.3136,0.2949,0.2902,
0.2710,0.2555,0.2506,0.2498,0.2303,0.2317,0.2283,0.2277,0.2310,0.2275,0.2250,
0.2216,0.2200,0.2155)
plot(x=1:26,y=silhouette.kmeans,lwd=1.2,type="o",pch=20,xlab="Number of clusters k",ylab="Average silhouette width",cex.lab=1.5,cex=1.5,col="purple",cex.axis=1.5, main="Fig S1E")

2d. Ensemble clustering- Part I
#First we perform ensemble clustering for k-means (with 1< k < 11)
#Number of bootstraps
N=5000
#Create co-clustering matrix
clustering.matrix<-matrix(ncol=26,nrow=26,0)
rownames(clustering.matrix)<-unique(sapply(1:ncol(normalized.counts.t15_average),function(x){strsplit(colnames(normalized.counts.t15_average)[x],split = "_")[[1]][1]}))
colnames(clustering.matrix)<-rownames(clustering.matrix)
for(n in 1:N)
{
#New time series with replicates from different reactors (one random replicate per time point)
permutation.sampling<-c()
for(i in 0:25)
{
#Location of current time point replicates in the normalized expression matrix
potential.point<- (i*3) + (1:3)
#Random selection of single replicate
selected.point<-potential.point[sample(1:3,1)]
#Add selected replicate to time series
permutation.sampling<-cbind(permutation.sampling,normalized.counts.t15_average[,selected.point])
}
colnames(permutation.sampling)<-colnames(clustering.matrix)
#Compute k-means with k in the [2,10] range
for(z in 2:10)
{
temp.kmeans.z<-kmeans(t(permutation.sampling),z,nstart = 25)
#Identify time points that clustered together
for(a in 1:z){
clustered.points<-names(which( temp.kmeans.z$cluster == a))
if(length(clustered.points)>1)
{
for(d in 1:length(clustered.points))
{
for(c in 1:length(clustered.points))
{
clustering.matrix[clustered.points[d],clustered.points[c]]<- clustering.matrix[clustered.points[d],clustered.points[c]]+1
}
}
}
else
{
clustering.matrix[clustered.points,clustered.points]<- clustering.matrix[clustered.points,clustered.points]+1
}
}
}
}
heatmap.2(clustering.matrix/(N*9),Rowv = "NA",dendrogram = "none",Colv="NA",trace = "none",col = colorRampPalette(rev(brewer.pal(20, "PiYG")) )(20),density.info="none",main="Fig S1F")
n too large, allowed maximum for palette PiYG is 11
Returning the palette you asked for with that many colors

2e. Ensemble clustering- Part II
#Now that the existence of six clusters has been confirmed. We perform ensemble clustering for k-means (k=6)
#Number of bootstraps
N=10000
#Create co-clustering matrix
clustering.matrix.200<-matrix(ncol=26,nrow=26,0)
rownames(clustering.matrix.200)<-unique(sapply(1:ncol(normalized.counts.t15_average),function(x){strsplit(colnames(normalized.counts.t15_average)[x],split = "_")[[1]][1]}))
colnames(clustering.matrix.200)<-rownames(clustering.matrix.200)
for(n in 1:N)
{
#New time series with replicates from different reactors (one random replicate per time point)
permutation.sampling<-c()
for(i in 0:25)
{
#Location of current time point replicates in the normalized expression matrix
potential.point<- (i*3) + (1:3)
#Random selection of single replicate
selected.point<-potential.point[sample(1:3,1)]
#Add selected replicate to time series
permutation.sampling<-cbind(permutation.sampling,normalized.counts.t15_average[,selected.point])
}
colnames(permutation.sampling)<-colnames(clustering.matrix.200)
#Run k-means with k=6
temp.kmeans.6<-kmeans(t(permutation.sampling),6,nstart = 25)
#Identify time points that clustered together
for(z in 1:6)
{
clustered.points<-names(which(temp.kmeans.6$cluster == z))
if(length(clustered.points)>1)
{
for(d in 1:length(clustered.points))
{
for(c in 1:length(clustered.points))
{
clustering.matrix.200[clustered.points[d],clustered.points[c]]<- clustering.matrix.200[clustered.points[d],clustered.points[c]]+1
}
}
}
else
{
clustering.matrix.200[clustered.points,clustered.points]<- clustering.matrix.200[clustered.points,clustered.points]+1
}
}
}
clustering.ensemble.200k<-clustering.matrix.200/N
heatmap.2(clustering.ensemble.200k,Rowv = "NA",dendrogram = "none",Colv="NA",trace = "none",col = colorRampPalette(rev(brewer.pal(20, "PiYG")) )(20),density.info="none",main="Fig S1G")
n too large, allowed maximum for palette PiYG is 11
Returning the palette you asked for with that many colors

2f. Compare inter- and intra-group variation using quantro (original run in R-3.6)
#quantro.stateLabels=c(rep("N",6),rep("D",18),rep("L",6),rep("E",3),rep("L",3),rep("E",12),rep("M",9),rep("L",9),rep("R",12))
#Remove genes with zero counts in all replicates
#zero.rows<-which(apply(FUN=max,MARGIN=1,normalized.counts.t15_average)==0)
#normalized.counts.reps<-normalized.counts.t15_average[-1*zero.rows,]
#Run Quantro with 10000 bootstraps
#qtest.normalized <- quantro(object = normalized.counts.reps, groupFactor = quantro.stateLabels,B = 10000)
#Plot result
#quantroPlot(qtest.normalized)
2g. Visualize clusters (states) with tSNE plot
#Run tSNE (using only DEGs to have best resolution)
clusters.colors<-c("blue","deepskyblue2","orange","darkolivegreen1","green3","red")
stateLabels=c(rep(1,2),rep(2,6),3,3,4,3,rep(4,4),rep(5,3),rep(3,3),rep(6,4))
tsne.input<-t(median.normalized.counts[DEGs.timecourse,])
tsne.output=Rtsne(tsne.input,dims=2,perplexity=7,verbose=TRUE)
Read the 26 x 26 data matrix successfully!
Using no_dims = 2, perplexity = 7.000000, and theta = 0.500000
Computing input similarities...
Normalizing input...
Building tree...
- point 0 of 26
Done in 0.00 seconds (sparsity = 0.914201)!
Learning embedding...
Iteration 50: error is 60.864247 (50 iterations in 0.02 seconds)
Iteration 100: error is 55.161538 (50 iterations in 0.01 seconds)
Iteration 150: error is 54.162451 (50 iterations in 0.01 seconds)
Iteration 200: error is 55.908350 (50 iterations in 0.01 seconds)
Iteration 250: error is 58.371151 (50 iterations in 0.01 seconds)
Iteration 300: error is 1.512479 (50 iterations in 0.00 seconds)
Iteration 350: error is 1.051103 (50 iterations in 0.00 seconds)
Iteration 400: error is 0.630091 (50 iterations in 0.00 seconds)
Iteration 450: error is 0.151097 (50 iterations in 0.01 seconds)
Iteration 500: error is 0.130585 (50 iterations in 0.01 seconds)
Iteration 550: error is 0.118387 (50 iterations in 0.01 seconds)
Iteration 600: error is 0.103883 (50 iterations in 0.00 seconds)
Iteration 650: error is 0.098280 (50 iterations in 0.00 seconds)
Iteration 700: error is 0.100665 (50 iterations in 0.00 seconds)
Iteration 750: error is 0.094375 (50 iterations in 0.00 seconds)
Iteration 800: error is 0.095730 (50 iterations in 0.00 seconds)
Iteration 850: error is 0.097345 (50 iterations in 0.01 seconds)
Iteration 900: error is 0.097089 (50 iterations in 0.01 seconds)
Iteration 950: error is 0.091347 (50 iterations in 0.01 seconds)
Iteration 1000: error is 0.095081 (50 iterations in 0.00 seconds)
Fitting performed in 0.13 seconds.
plot(tsne.output$Y,col=clusters.colors[stateLabels],pch=19,xlab='tSNE Component 1',ylab='tSNE Component 2',main="Fig 2A")
text(x=tsne.output$Y[,1],y=tsne.output$Y[,2],rownames(tsne.input))

3a. Define gene clusters based on clusters-average expression profile
#The six states defined in Fig 2A
transcriptional.states<-c("Normoxia","Depletion","Early","Mid","Late","Resuscitation")
#Time points-states association
time.points.clusters<-list(c(1,2),3:8,c(11,13:16),17:19,c(9,10,12,20:22),23:26)
names(time.points.clusters)<-transcriptional.states
gene.clusters<-rep(list(NULL),length(transcriptional.states))
#Assign each DEG to one transcriptional state
for(f in DEGs.timecourse)
{
#Initialize maximum average expression and state membership
max.level<-mean(median.normalized.counts[f,time.points.clusters[[1]]])
gene.state<-1
#Evaluate if the average is higher in any of the other states
for (s in 2:6)
{
temp.mean<-mean(median.normalized.counts[f,time.points.clusters[[s]]])
#Update state and maximum average
if (temp.mean > max.level)
{
gene.state <- s
max.level<-temp.mean
}
}
gene.clusters[[gene.state]]<-c(gene.clusters[[gene.state]],f)
}
names(gene.clusters)<-transcriptional.states
#This function computes bicluster residual using formula from Cheng and Church, 2000
bc.residual<-function(genes,conditions,exp.mat)
{
volume<-length(genes)*length(conditions)
D_IJ<-mean(exp.mat[genes,conditions])
residual<-0
for(f in genes)
{
for(z in conditions)
{
temp.r<-exp.mat[f,z] - mean(exp.mat[f,conditions]) - mean(exp.mat[genes,z]) + D_IJ
residual<- residual + (temp.r)^2
}
}
residual<- residual/volume
residual
}
#This function computes the average Pearson correlation of a group of genes
cluster.correlation<-function(genes)
{
all.correlations<-c()
for(w in 1:(length(genes)-1))
{
for(t in (w+1):length(genes))
{
all.correlations<-c(all.correlations,cor(median.normalized.counts[genes[w],],median.normalized.counts[genes[t],]))
}
}
output<-mean(all.correlations)
output
}
3b. Comparison with Boruta clustering (random forest based approach)
#Each state vs the other five states
bestPartition<-c(0,0,1,1,1,1,1,1,4,4,2,4,2,2,2,2,3,3,3,4,4,4,5,5,5,5)
boruta.signature.genes<-list()
for(z in 0:5)
{
hypoxia.df<-as.data.frame(cbind(bestPartition,t(median.normalized.counts[DEGs.timecourse,])))
hypoxia.df$bestPartition[which(hypoxia.df$bestPartition != z)]<- 100
hypoxia.df$bestPartition<-as.factor(hypoxia.df$bestPartition)
set.seed(123)
boruta.train <- Boruta(bestPartition~., data = hypoxia.df, doTrace = 2)
final.boruta <- TentativeRoughFix(boruta.train)
print(final.boruta)
boruta.signature.genes[[z+1]]<-names(final.boruta$finalDecision[which(final.boruta$finalDecision == "Confirmed")])
}
Boruta performed 99 iterations in 13.59856 secs.
Tentatives roughfixed over the last 99 iterations.
15 attributes confirmed important: Rv0064, Rv0421c, Rv0451c, Rv0713, Rv0755c and 10 more;
2567 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0009, Rv0010c and 2562
more;
Boruta performed 99 iterations in 20.47355 secs.
Tentatives roughfixed over the last 99 iterations.
127 attributes confirmed important: Rv0009, Rv0010c, Rv0042c, Rv0055, Rv0096 and 122 more;
2455 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0011c, Rv0013 and 2450
more;
Boruta performed 99 iterations in 19.75365 secs.
Tentatives roughfixed over the last 99 iterations.
80 attributes confirmed important: Rv0040c, Rv0115, Rv0147, Rv0207c, Rv0241c and 75 more;
2502 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0009, Rv0010c and 2497
more;
Boruta performed 99 iterations in 15.5174 secs.
Tentatives roughfixed over the last 99 iterations.
44 attributes confirmed important: Rv0064, Rv0237, Rv0285, Rv0328, Rv0827c and 39 more;
2538 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0009, Rv0010c and 2533
more;
Boruta performed 99 iterations in 31.77508 secs.
Tentatives roughfixed over the last 99 iterations.
254 attributes confirmed important: Rv0032, Rv0077c, Rv0095c, Rv0106, Rv0124 and 249 more;
2328 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0009, Rv0010c and 2323
more;
Boruta performed 99 iterations in 17.68268 secs.
Tentatives roughfixed over the last 99 iterations.
94 attributes confirmed important: Rv0065, Rv0113, Rv0125, Rv0251c, Rv0644c and 89 more;
2488 attributes confirmed unimportant: Rv0001, Rv0005, Rv0006, Rv0009, Rv0010c and 2483
more;
names(boruta.signature.genes)<-c("Normoxia","Depletion","Early","Mid","Late","Resuscitation")
#Now we create Table S3
tableS3<-matrix(nrow=length(gene.clusters),ncol=6,NA,dimnames = list(transcriptional.states,c("Genes selected based on mean", "Mean Square Residual","Pearson correlation", "# Boruta genes","Overlap", "Recall-Boruta")))
for(z in rownames(tableS3))
{
current.state.genes<-gene.clusters[[z]]
tableS3[z,1]<-length(current.state.genes)
tableS3[z,2]<-bc.residual(genes = current.state.genes,conditions =time.points.clusters[[z]] ,exp.mat = median.normalized.counts )
tableS3[z,3]<-cluster.correlation(current.state.genes)
tableS3[z,4]<-length(boruta.signature.genes[[z]])
tableS3[z,5]<-length(intersect(boruta.signature.genes[[z]],current.state.genes))
tableS3[z,6]<-round(tableS3[z,5]/tableS3[z,4],digits = 2)
}
Print Table S3
print(tableS3)
Genes selected based on mean Mean Square Residual Pearson correlation # Boruta genes
Normoxia 81 0.04448027 0.2814294 15
Depletion 446 0.01494834 0.7135277 127
Early 328 0.05303711 0.5470933 80
Mid 320 0.07686766 0.4000902 44
Late 978 0.02790105 0.7456892 254
Resuscitation 429 0.08484841 0.4753521 94
Overlap Recall-Boruta
Normoxia 3 0.20
Depletion 73 0.57
Early 61 0.76
Mid 24 0.55
Late 185 0.73
Resuscitation 56 0.60
3c. Compare the identified transcriptional states with previous hypoxia models (Table S1)
#Initial hypoxic response
IHR<-scan("IHR_genes.csv",what="character")
Read 49 items
#Enduring hypoxic response
EHR<-scan("EHR_genes.csv",what="character")
Read 230 items
NRP<-read.csv("nrp_data.csv",header=T,row.names=1)
#Define Non replicating persistence 1 genes
NRP1<-rownames(NRP)[which(NRP$pval1 <= 0.05 & NRP$Ratio1 > 2)]
#Define Non replicating persistence 2 genes
NRP2<-rownames(NRP)[which(NRP$pval2 <= 0.05 & NRP$Ratio2 > 2)]
#Comparison matrix
others.hypoxia.models<-c("IHR","EHR","NRP1","NRP2")
tableS1<-matrix(ncol=8,nrow=6,NA,dimnames = list(transcriptional.states,c("IHR recall","IHR P-value","EHR recall","EHR P-value","NRP1 recall","NRP1 P-value","NRP2 recall","NRP2 P-value")))
for(i in names(gene.clusters))
{
for(j in others.hypoxia.models)
{
overlap<-intersect(gene.clusters[[i]],eval(parse(text=j)))
tableS1[i,paste(j,"recall",sep=" ")]<-length(overlap)/length(eval(parse(text=j)))
pval<-phyper(length(overlap)-1,length(eval(parse(text=j))),nrow(median.normalized.counts)-length(eval(parse(text=j))),length(gene.clusters[[i]]),lower.tail=F)
tableS1[i,paste(j,"P-value",sep=" ")]<-pval
}
}
tableS1<-round(tableS1,digits = 3)
print(tableS1)
IHR recall IHR P-value EHR recall EHR P-value NRP1 recall NRP1 P-value NRP2 recall
Normoxia 0.000 1.000 0.004 0.992 0.016 0.731 0.004
Depletion 0.020 0.997 0.035 1.000 0.107 0.593 0.040
Early 0.408 0.000 0.070 0.779 0.176 0.000 0.138
Mid 0.490 0.000 0.226 0.000 0.187 0.000 0.201
Late 0.041 1.000 0.196 0.963 0.032 1.000 0.125
Resuscitation 0.020 0.996 0.165 0.003 0.316 0.000 0.214
NRP2 P-value
Normoxia 0.991
Depletion 1.000
Early 0.002
Mid 0.000
Late 1.000
Resuscitation 0.000
3d.Define the set of differentialy expressed genes in Galagan et al. (2013) and evaluate recall of differentially expressed genes in intracellular MTB (collected with Path-seq) (Table S2)
#Load Galagan's data (triplicates for each time point)
galagan.raw.data<-read.csv("galagan_raw_hypoxia_ts.csv",row.names=1)
#Perform differential expression analysis with bayesian T-test using Cyber-T (Baldi and Long, 2001)
source("cyberTtest.R")
#List with the output of differential expression analyses
galagan.dea<-list()
#List with DEGs at each time point
galagan.deg<-list()
galagan.DEGs.total<-c()
for(t in 1:6)
{
galagan.dea[[t]]<-bayesT(aData = galagan.raw.data[,c(1:3,(1+(3*t)):(3+(3*t)))],numC = 3,numE = 3,conf = 7,doMulttest = T)
galagan.deg[[t]]<-rownames(galagan.dea[[t]])[which(galagan.dea[[t]]$BH < 0.05 & abs(galagan.dea[[t]]$meanC - galagan.dea[[t]]$meanE)> 1)]
galagan.DEGs.total<-union(galagan.DEGs.total,galagan.deg[[t]])
}
#load list of DEGs identified with Path-seq (Peterson et al, 2019)
intracellular.genes<-scan("DEGs_intracellular_MTB_pathseq.txt",what="character")
Read 932 items
tableS2<-matrix(nrow=4,ncol=3,NA,dimnames=list(c("Controlled O2","Galagan13","Wayne","Defined"),c("Overlap w/ intracellular genes","Recall","P-value")))
tableS2[1,"Overlap w/ intracellular genes"]<-length(intersect(intracellular.genes,DEGs.timecourse))
tableS2[1,"Recall"]<-round(tableS2[1,1]/length(intracellular.genes),digits = 3)
tableS2[1,"P-value"]<-phyper(tableS2[1,1]-1,length(intersect(rownames(median.normalized.counts),intracellular.genes)),nrow(median.normalized.counts)-length(intersect(rownames(median.normalized.counts),intracellular.genes)),length(DEGs.timecourse),lower.tail = F)
tableS2[2,"Overlap w/ intracellular genes"]<-length(intersect(intracellular.genes,galagan.DEGs.total))
tableS2[2,"Recall"]<-round(tableS2[2,1]/length(intracellular.genes),digits = 3)
tableS2[2,"P-value"]<-phyper(tableS2[2,1]-1,length(intersect(rownames(galagan.raw.data),intracellular.genes)),nrow(galagan.raw.data)-length(intersect(rownames(galagan.raw.data),intracellular.genes)),length(galagan.DEGs.total),lower.tail = F)
tableS2[3,"Overlap w/ intracellular genes"]<-length(intersect(intracellular.genes,union(NRP1,NRP2)))
tableS2[3,"Recall"]<-round(tableS2[3,1]/length(intracellular.genes),digits = 3)
#There are 4,094 genes in the Path-seq expression dataset, so we take that number as the total population size in the hypergeometric test
tableS2[3,"P-value"]<-phyper(tableS2[3,1]-1,length(intracellular.genes),4094-length(intracellular.genes),length(union(NRP1,NRP2)),lower.tail = F)
tableS2[4,"Overlap w/ intracellular genes"]<-length(intersect(intracellular.genes,union(IHR,EHR)))
tableS2[4,"Recall"]<-round(tableS2[4,1]/length(intracellular.genes),digits = 3)
tableS2[4,"P-value"]<-phyper(tableS2[4,1]-1,length(intracellular.genes),4094-length(intracellular.genes),length(union(IHR,EHR)),lower.tail = F)
print(tableS2)
Overlap w/ intracellular genes Recall P-value
Controlled O2 731 0.784 1.738885e-30
Galagan13 711 0.763 1.042124e-01
Wayne 115 0.123 1.185192e-10
Defined 117 0.126 2.099120e-14
3d. Create Fig 2B draft
clusters.colors<-c("blue","deepskyblue2","darkolivegreen1","green3","orange","red")
names(clusters.colors)<-transcriptional.states
time.vector<-scan("time_vector.txt")
Read 26 items
time.vector<-round(time.vector,digits = 2)
#We summarized common themes of significant functional terms defined by DAVID (Huang da et al, 2009)
DAVID.enriched.selected.terms<-as.matrix(rbind(c(52,31,17,14,10,10,17,30,36,5,36,14,23,15,8,9),c("Translation","Oxididative phos.","Aa met.","ATP synthesis","ETC","Lipid met.","Aa met.","Response to hypoxia","Transcription regulation","Histidine synthesis",
"PPE","Cobalamin synthesis","Mammalian cell entry","ESAT-6 like*","Proteases","DNA transposition"),rep(transcriptional.states[2:6],times=c(3,3,4,3,3))))
rownames(DAVID.enriched.selected.terms)<-c("Number of genes","Term","State")
#Scale normalized read counts in the [0,1] interval
scale.counts<-function(countMatrix)
{
output<-c()
for(q in rownames(countMatrix))
{
min.counts<-min(countMatrix[q,])
max.counts<-max(countMatrix[q,])
counts.range<-max.counts - min.counts
output<- rbind(output,(countMatrix[q,]-min.counts)/counts.range)
}
rownames(output)<-rownames(countMatrix)
output
}
scaled.normalized.median.counts<-scale.counts(median.normalized.counts)
rownames(scaled.normalized.median.counts)<-rownames(median.normalized.counts)
par(mfrow=c(6,3))
par(mar=c(1,1,1,1))
for(j in 1:6)
{
current.state<-transcriptional.states[j]
mean.profile<-colMeans(scaled.normalized.median.counts[gene.clusters[[j]],])
plot(x=time.vector,y=mean.profile,ylim=c(0,1),main="",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
text(current.state,x=20,y=0.1)
frame()
if (j == 1)
{
frame()
}
else
{
barplot(as.numeric(DAVID.enriched.selected.terms["Number of genes",which(DAVID.enriched.selected.terms["State",]==current.state)]),cex.lab=1,
names=DAVID.enriched.selected.terms["Term",which(DAVID.enriched.selected.terms["State",]==current.state)],col=rep(clusters.colors[j],length(which(DAVID.enriched.selected.terms["State",]==current.state))),las=2,horiz = T,xlim=c(0,50))
}
}

3e. Create figure similar to Fig 2b but for each reactor
#Define expression matrix for each replicate
repA<-normalized.counts.t15_average[,grep("A",colnames(normalized.counts.t15_average))]
colnames(repA)<-paste("T",0:25,"A",sep="")
repB<-normalized.counts.t15_average[,grep("B",colnames(normalized.counts.t15_average))]
colnames(repB)<-paste("T",0:25,"B",sep="")
repC<-normalized.counts.t15_average[,grep("C",colnames(normalized.counts.t15_average))]
colnames(repC)<-paste("T",0:25,"C",sep="")
repA.scaled<-scale.counts(repA)
repB.scaled<-scale.counts(repB)
repC.scaled<-scale.counts(repC)
par(mfrow=c(6,3))
par(mar=c(1,1,1,1))
for(j in 1:6)
{
current.state<-transcriptional.states[j]
mean.profile.A<-colMeans(repA.scaled[gene.clusters[[j]],])
if(j==1)
{
plot(x=time.vector,y=mean.profile.A,ylim=c(0,1),main="Replicate A",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
else{
plot(x=time.vector,y=mean.profile.A,ylim=c(0,1),main="",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
text(current.state,x=20,y=0.1)
mean.profile.B<-colMeans(repB.scaled[gene.clusters[[j]],])
if(j==1)
{
plot(x=time.vector,y=mean.profile.B,ylim=c(0,1),main="Replicate B",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
else
{
plot(x=time.vector,y=mean.profile.B,ylim=c(0,1),main="",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
text(current.state,x=20,y=0.1)
mean.profile.C<-colMeans(repC.scaled[gene.clusters[[j]],])
if(j==1)
{
plot(x=time.vector,y=mean.profile.C,ylim=c(0,1),main="Replicate C",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
else{
plot(x=time.vector,y=mean.profile.C,ylim=c(0,1),main="",xlab="Time",ylab="Scaled counts",type="o",pch=1,col=clusters.colors[j],cex.lab=1,cex.axis=0.7)
}
text(current.state,x=20,y=0.1)
}

3f. Create draft of Fig 2C-F
stateLabels=c(rep(1,2),rep(2,6),5,5,3,5,rep(3,4),rep(4,3),rep(5,3),rep(6,4))
#Data from Kavvas et al. (2018)
metabolic.pathways.tab<-read.csv("metabolic_pathways.csv",header=TRUE)
metabolic.genes<-unique(scan(file="mtb_metabolic_model_genes.txt",what="characters"))
Read 6179 items
#Filter out non-coding elements
metabolic.genes<-metabolic.genes[grep("Rv",metabolic.genes)]
#Create table with the pathways relevant to each gene
gene.path<-c()
for(t in metabolic.genes)
{
pos.path<-grep(t,metabolic.pathways.tab$Gene.Reaction.Rule)
relevant.path<-unique(as.character(metabolic.pathways.tab$Subsystem[pos.path]))
temp.vec<-cbind(rep(t,length(relevant.path)),relevant.path)
gene.path<-rbind(gene.path,temp.vec)
}
all.pathways<-unique(gene.path[,2])
#Pathways shown in Fig 2
selected.pathways<-all.pathways[c(35,41,7,47)]
par(mfrow=c(2,2))
for(z in selected.pathways)
{
genes.in.path<-intersect(DEGs.timecourse,unique(gene.path[which(gene.path[,2]==z),1]))
#Compute fold-change respect to mean normoxia transcript levels (T0 & T1)
foldchanges<-median.normalized.counts[genes.in.path,3:26]-rowMeans(median.normalized.counts[genes.in.path,time.points.clusters$Normoxia])
boxplot(foldchanges,main=paste(z," (",length(genes.in.path),")",sep=""),names=round(time.vector,digits = 1)[-1*(1:2)],las=2,ylab="Log2 fold-change",outline=F,cex.lab=1.5,col=clusters.colors[stateLabels[-1*c(1:2)]])
}

3g. Create Fig S3
#The five type seven secretion systems (T7SS) of MTB
ESX1<-c("Rv3868", "Rv3869", "Rv3877", "Rv3870", "Rv3871", "Rv3882c", "Rv3883c")
ESX2<-c("Rv3884c", "Rv3885c", "Rv3887c", "Rv3894c", "Rv3895c", "Rv3889c")
ESX3<-c("Rv0282", "Rv0283", "Rv0284", "Rv0290", "Rv0292", "Rv0289")
ESX4<-c("Rv3450c", "Rv3447c", "Rv3448")
ESX5<-c("Rv1782", "Rv1783", "Rv1795", "Rv1797", "Rv1798")
par(mfrow=c(2,2))
for(i in 1:5)
{
genes<-eval(parse(text=paste("ESX",i,sep="")))
esx.colors<-rainbow(length(genes))
plot(x=time.vector,y=scaled.normalized.median.counts[genes[1],],xlab="Time (h)",col=esx.colors[1],ylab="Scaled Normalized counts",type="o",main=paste("ESX",i,sep=""),pch=16,ylim=c(0,1))
for(d in 2:length(genes))
{
points(x=time.vector,y=scaled.normalized.median.counts[genes[d],],type="o",col=esx.colors[d],pch=16)
}
}


- Create heatmap with transcriptional profile of differentially expressed (DE) TFs (Fig 3B)
#Load TF over-expression data (Rustad et al 2014)
mtb.exp<-read.table("tfoe_data.txt",header=T,row.names = 1)
de.tfs<-intersect(colnames(mtb.exp),DEGs.timecourse)
de.tfs.ordered<-c()
tfs.colors<-rep("NA",length(de.tfs))
names(tfs.colors)<-de.tfs
for(i in transcriptional.states)
{
de.tfs.current.state<-as.character(intersect(de.tfs,gene.clusters[[i]]))
de.tfs.current.state<-de.tfs.current.state[order(de.tfs.current.state,decreasing = F)]
de.tfs.ordered<-c(de.tfs.ordered,de.tfs.current.state)
tfs.colors[de.tfs.current.state]<-rep(clusters.colors[i],length(de.tfs.current.state))
}
colors <- colorRampPalette(rev(brewer.pal(9, "YlGnBu")) )(99)
heatmap.2(median.normalized.counts[de.tfs.ordered,],Rowv = "NA",dendrogram = "none",Colv="NA",trace = "none",scale="row",col = colors,density.info="none",cexRow=0.4,main="Fig 3B",colRow = tfs.colors[de.tfs.ordered])

- Compare fold-change of DEGs in Rv0081 KO MTB (hypoxia) versus their fold-change in the Rv0081 over-expressing MTB (normoxia)
#Load ChIP-seq data (Minch et al. 2015)
mtb.chip<-read.csv("final_chipseq_binding.csv",header=T)
#Define Rv0081 regulon
Rv0081.regulon<-mtb.chip[which(mtb.chip[,1]=="Rv0081"),2]
#Load Rv0081 KO data (Sun et al. 2018)
rv0081.deg.ko<-read.csv("Rv0081_ko_hypoxia.csv",header=T,row.names=1)
#Fix locus names issue (not all of them have a locus name)
names.to.change<-rownames(rv0081.deg.ko)[grep("Rv",rownames(rv0081.deg.ko),invert=T)]
#Use MicrobesOnline data to convert gene names to loci names
mtb.names.tab1<-read.table("mtb_microbesonline.txt",fill=T,header=T)
mtb.names.tab1<-unique.matrix(mtb.names.tab1)
new.names<-c()
for(h in names.to.change)
{
pos.g<-which(mtb.names.tab1[,2]==h)
if(length(pos.g)>0)
{
new.names<-c(new.names,as.character(mtb.names.tab1[pos.g,1]))
}
else
{
new.names<-c(new.names,h)
}
}
rownames(rv0081.deg.ko)[rownames(rv0081.deg.ko) %in% names.to.change]<-new.names
#Same idea with information from the MTB Network portal
mtb.names.tab2<-read.table("mtb_portal_genes2111.txt",fill=T)
mtb.names.tab2<-unique.matrix(mtb.names.tab2)
names.to.change<-rownames(rv0081.deg.ko)[grep("Rv",rownames(rv0081.deg.ko),invert=T)]
new.names<-c()
for(h in names.to.change)
{
pos.g<-which(mtb.names.tab2[,2]==h)
if(length(pos.g)>0)
{
new.names<-c(new.names,as.character(mtb.names.tab2[pos.g,1]))
}
else
{
new.names<-c(new.names,h)
}
}
rownames(rv0081.deg.ko)[rownames(rv0081.deg.ko) %in% names.to.change]<-new.names
#Rv0081 targets that were DE in Rv0081 KO
rv0081.regulon.de<-intersect(rownames(rv0081.deg.ko),Rv0081.regulon)
#mtb.exp contains the data for Rv0081 over-expression
#First plot Rv0081 targets (according to ChIP-seq data) that were DE in the Rv0081 KO
plot(rv0081.deg.ko[rv0081.regulon.de,"Log2.._Rv0081.WT."],mtb.exp[rv0081.regulon.de,"Rv0081"],main="Fig S4A",xlab="Log2 fold-change Rv0081 KO (Hypoxia)",ylab="Log2 fold-change Rv0081 over-expression (Normoxia)",col="red",pch=1,ylim=c(-3,5.5),xlim=c(-9.5,3.5))
other.de.genes.in.rv0081.ko<-intersect(rownames(mtb.exp),setdiff(rownames(rv0081.deg.ko),rv0081.regulon.de))
# Add genes that are not Rv0081 targets (according to ChIP-seq data)
points(rv0081.deg.ko[other.de.genes.in.rv0081.ko,"Log2.._Rv0081.WT."],mtb.exp[other.de.genes.in.rv0081.ko,"Rv0081"],col="dark green",pch=1)
abline(lty=2,col="grey",h=1)
abline(lty=2,col="grey",h=-1)
abline(lty=2,col="grey",v=1)
abline(lty=2,col="grey",v=-1)
legend("topleft",legend=c("Rv0081 target (ChIP-seq)","Not Rv0081 target (ChIP-seq)"),col=c("red","dark green"),pt.cex=1,bty="n",cex=0.65,pch=1)

6a. Create Fig 4A-B
top.ffl<-list(c("Rv0081","Rv3249c"),c("Rv0081","Rv0324"))
#Rv0081-Rv3249c FFL
tf.pair<-top.ffl[[1]]
par(mfrow=c(1,3))
#Depletion-associated genes
depletion.genes<-gene.clusters[["Depletion"]]
not.DEGs<-setdiff(rownames(median.normalized.counts),DEGs.timecourse)
main.tf.regulon<-mtb.chip[which(mtb.chip[,1]==tf.pair[1]),2]
secondary.tf.regulon<-mtb.chip[which(mtb.chip[,1]==tf.pair[2]),2]
ffl.targets<-intersect(main.tf.regulon,secondary.tf.regulon)
depletion.genes.not.bound.by.main.tf<-setdiff(depletion.genes,main.tf.regulon)
depletion.genes.bound.by.main.tf.not.second<-setdiff(intersect(depletion.genes,main.tf.regulon),ffl.targets)
depletion.genes.bound.by.ffl<-intersect(depletion.genes,ffl.targets)
hist(rv0081.deg.ko[intersect(depletion.genes,rownames(rv0081.deg.ko)),"Log2.._Rv0081.WT."],col="cadetblue2",main="Fig 4A",
xlab="Log2 fold-change (Rv0081 KO vs WT)")
boxplot(mtb.exp[not.DEGs,"Rv0081"],mtb.exp[depletion.genes.not.bound.by.main.tf,"Rv0081"],mtb.exp[depletion.genes.bound.by.main.tf.not.second,"Rv0081"],mtb.exp[depletion.genes.bound.by.ffl,"Rv0081"],las=2,names=c("A(1467)","B(352)","C(62)","D(32)"),col=c("grey",rep("cadetblue2",3)),ylab="Log2 fold-change (Rv0081 TFOE vs WT)",cex.axis=1,cex.lab=1,main="Fig 4B")
plot.new()
legend("topleft",legend=c("A:not DE in time course","B:not controlled by Rv0081","C:controlled by Rv0081 but not Rv3249c ","D:controlled by Rv0081-Rv3249c FFL"),col="white",pt.cex=1,bty="n",cex=0.74,pch=1)

6b. Create Fig 4C-D
#Rv0081-Rv0324
tf.pair<-top.ffl[[2]]
#Late hypoxia-associated genes
late.genes<-gene.clusters[["Late"]]
main.tf.regulon<-mtb.chip[which(mtb.chip[,1]==tf.pair[1]),2]
secondary.tf.regulon<-mtb.chip[which(mtb.chip[,1]==tf.pair[2]),2]
ffl.targets<-intersect(main.tf.regulon,secondary.tf.regulon)
late.genes.not.bound.by.main.tf<-setdiff(late.genes,main.tf.regulon)
late.genes.bound.by.main.tf.not.second<-setdiff(intersect(late.genes,main.tf.regulon),ffl.targets)
late.genes.bound.by.ffl<-intersect(late.genes,ffl.targets)
par(mfrow=c(1,3))
hist(rv0081.deg.ko[intersect(late.genes,rownames(rv0081.deg.ko)),"Log2.._Rv0081.WT."],col="peachpuff",main="Fig 4C",
xlab="Log2 fold-change (Rv0081 KO vs WT)")
boxplot(mtb.exp[not.DEGs,"Rv0081"],mtb.exp[late.genes.not.bound.by.main.tf,"Rv0081"],mtb.exp[late.genes.bound.by.main.tf.not.second,"Rv0081"],mtb.exp[late.genes.bound.by.ffl,"Rv0081"],names=c("A(1467)","B(818)","C(99)","D(61)"),las=2,col=c("grey",rep("peachpuff",3)),ylab="Log2 fold-change (Rv0081 TFOE vs WT)",cex.axis=1,cex.lab=1,main="Fig 4D")
plot.new()
legend("topleft",legend=c("A:not DE in time course","B:not controlled by Rv0081","C:controlled by Rv0081 but not Rv0324 ","D:controlled by Rv0081-Rv0324 FFL"),col="white",pt.cex=1,bty="n",cex=0.74,pch=1)

- Generate input files for MotifNet server (FFL detection) #MotifNet run is temporarily available in: http://netbio.bgu.ac.il/motifnet/#/explorer/1414
input.network<-c()
for(k in 1:nrow(mtb.chip))
{
current.tf<-mtb.chip[k,1]
current.target<-mtb.chip[k,2]
if(current.tf %in% DEGs.timecourse & current.target %in% DEGs.timecourse )
{
input.network<-rbind(input.network,mtb.chip[k,])
}
}
input.nodes<-union(input.network[,1],input.network[,2])
#Write.files
write.table(file="input_network.txt",input.network,quote = F,row.names = F,sep = "\t")
write.table(file="input_nodes.txt",input.nodes,quote = F,row.names = F)
8a. Evaluate overlap between TF regulons and the identified transcriptional states - Part I
regulons.enriched.with.own.state<-c()
for(z in de.tfs)
{
current.regulon<-intersect(mtb.chip[which(mtb.chip[,1]==z),2],DEGs.timecourse)
#Define the state of each DE TF
#Default value is Normoxia
tf.state<-1
for(x in 2:6)
{
if(length(grep(z,gene.clusters[[x]]))>0)
{
tf.state<-x
}
}
members.current.regulon.in.TF.state<-intersect(current.regulon,gene.clusters[[tf.state]])
regulons.enriched.with.own.state.pvalue<-phyper(length(members.current.regulon.in.TF.state)-1,length(gene.clusters[[tf.state]]),length(DEGs.timecourse)-length(gene.clusters[[tf.state]]),length(current.regulon),lower.tail = F)
if(regulons.enriched.with.own.state.pvalue <= 0.05)
{
regulons.enriched.with.own.state<-c(regulons.enriched.with.own.state,z)
}
}
print(paste("There are",length(regulons.enriched.with.own.state), "regulons enriched with genes assigned to the same transcriptional state as the regulating TF",sep=" "))
[1] "There are 21 regulons enriched with genes assigned to the same transcriptional state as the regulating TF"
#Permutation test to evaluate significance of enrichment instances
#Number of permutations
N=1000
count.regulons.randomly.enriched.with.same.state<-c()
for(w in 1:N)
{
#Shuffled DEGs state-membership
shuffled.degs<-DEGs.timecourse[sample(1:length(DEGs.timecourse),length(DEGs.timecourse))]
random.states<-list()
for(i in 1:6)
{
random.states[[i]]<-shuffled.degs[1:length(gene.clusters[[i]])]
shuffled.degs<-shuffled.degs[-1*(1:length(random.states[[i]]))]
}
#Evaluate random enrichment
regulons.randomly.enriched.with.own.state<-c()
for(z in de.tfs)
{
current.regulon<-intersect(mtb.chip[which(mtb.chip[,1]==z),2],DEGs.timecourse)
#Define the state of each DE TF
#Default value is Normoxia
tf.state<-1
for(x in 2:6)
{
if(length(grep(z,random.states[[x]]))>0)
{
tf.state<-x
}
}
members.current.regulon.in.random.TF.state<-intersect(current.regulon,random.states[[tf.state]])
regulons.randomly.enriched.with.own.state.pvalue<-phyper(length(members.current.regulon.in.random.TF.state)-1,length(random.states[[tf.state]]),length(DEGs.timecourse)-length(random.states[[tf.state]]),length(current.regulon),lower.tail = F)
if(regulons.randomly.enriched.with.own.state.pvalue <= 0.05)
{
regulons.randomly.enriched.with.own.state<-c(regulons.randomly.enriched.with.own.state,z)
}
}
count.regulons.randomly.enriched.with.same.state<-c(count.regulons.randomly.enriched.with.same.state,length(regulons.randomly.enriched.with.own.state))
}
#Permutation p-value
print(length(which(count.regulons.randomly.enriched.with.same.state >= length(regulons.enriched.with.own.state))))
[1] 0
8b. Evaluate overlap between TF regulons and the identified transcriptional states - Part II
regulons.enriched.with.other.state<-c()
for(z in de.tfs)
{
current.regulon<-intersect(mtb.chip[which(mtb.chip[,1]==z),2],DEGs.timecourse)
#Define the state of each DE TF
#Default value is Normoxia
tf.state<-1
for(x in 2:6)
{
if(length(grep(z,gene.clusters[[x]]))>0)
{
tf.state<-x
}
}
for(q in setdiff(1:6,tf.state))
{
members.current.regulon.in.other.state<-intersect(current.regulon,gene.clusters[[q]])
enrichment.with.other.state.pvalue<-phyper(length(members.current.regulon.in.other.state)-1,length(gene.clusters[[q]]),length(DEGs.timecourse)-length(gene.clusters[[q]]),length(current.regulon),lower.tail = F)
if(enrichment.with.other.state.pvalue <= 0.05)
{
regulons.enriched.with.other.state<-c(regulons.enriched.with.other.state,z)
}
}
}
print(paste("There are",length(unique(regulons.enriched.with.other.state)), "regulons enriched with genes assigned a transcriptional state different from the one of the regulating TF",sep=" "))
[1] "There are 49 regulons enriched with genes assigned a transcriptional state different from the one of the regulating TF"
LS0tCnRpdGxlOiAiUiBjb2RlIGZvciBjb21wdXRhdGlvbmFsIGFuYWx5c2VzIHJlcG9ydGVkIGluIEludHJpY2F0ZSBHZW5ldGljIFByb2dyYW1zIENvbnRyb2xsaW5nIERvcm1hbmN5IGluIE15Y29iYWN0ZXJpdW0gdHViZXJjdWxvc2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBNYXJpbyBBcnJpZXRhLU9ydGl6IGV0IGFsLiAoSW5zdGl0dXRlIGZvciBTeXN0ZW1zIEJpb2xvZ3ksIEJhbGlnYSBMYWIpCmRhdGU6IE9jdG9iZXIgMzAsIDIwMTkKLS0tCjAuIExvYWQgcmVxdWlyZWQgbGlicmFyaWVzCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoQm9ydXRhKQpsaWJyYXJ5KGNsVmFsaWQpCmxpYnJhcnkoREVTZXEyKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkoZ3Bsb3RzKQpsaWJyYXJ5KG11bHR0ZXN0KQpsaWJyYXJ5KE5iQ2x1c3QpIApsaWJyYXJ5KHB2Y2x1c3QpCiNsaWJyYXJ5KHF1YW50cm8pICNUaGlzIHBhY2thZ2Ugd2FzIHVzZWQgaW4gUi0zLjYgdmVyc2lvbiAKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocmdsKQpsaWJyYXJ5KFJ0c25lKQpsaWJyYXJ5KHNjYXR0ZXJwbG90M2QpCmxpYnJhcnkodmlyaWRpcykKYGBgCjEuIFJ1biBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB1c2luZyBERVNlcTIgKGFzIHN1Z2dlc3RlZCBpbiBERVNlcTIncyB2aWduZXR0ZTsgaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvZGV2ZWwvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sKQpgYGB7ciBtZXNzYWdlPUZBTFNFfQojTG9hZCByYXcgcmVhZCBjb3VudHMgKGRldGVybWluZWQgd2l0aCBEdWZmeU5HUykKI0FsbCB0aW1lIHBvaW50cyBoYXZlIHRocmVlIHJlcGxpY2F0ZXMgYnV0IFQxNSAod2hpY2ggaGFzIHNpeCByZXBsaWNhdGVzKQpjb3VudHMuaHlwb3hpYTwtYXMubWF0cml4KHJlYWQuY3N2KCJoeXBveGlhX3Jhd19yZWFkcy5jc3YiLHJvdy5uYW1lcz0xKSkKI1JlcGxhY2UgZmV3IE5BIHZhbHVlcyB3aXRoIHplcm9lcyAoMC4wNDklKQpuYS5oeXBveGlhPC13aGljaChpcy5uYShjb3VudHMuaHlwb3hpYSkpCmNvdW50cy5oeXBveGlhW25hLmh5cG94aWFdPC0wCiNQcmludCB0aW1lIHBvaW50cy9yZXBsaWNhdGVzIGluIHRoZSBleHByZXNzaW9uIG1hdHJpeApwcmludChjb2xuYW1lcyhjb3VudHMuaHlwb3hpYSkpCiNDcmVhdGUgbGlzdCB3aXRoIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcykgYXQgZWFjaCB0aW1lIHBvaW50IApkZS5nZW5lczwtbGlzdCgpCiNTYXZlIG5hbWVzIG9mIGFsbCBERUdzCkRFR3MudGltZWNvdXJzZTwtYygpCiNERVNlcTIgYW5hbHlzaXMgaXMgcGVyZm9ybWVkIGZvciBlYWNoIHRpbWUgcG9pbnQgd2l0aCByZXNwZWN0IHRvIFQwCmZvcihpIGluIGMoMjoxNiwxODoyNykpCnsKICAjRm9yIHRpbWUgcG9pbnRzIGRpZmZlcmVudCB0byBUMTUKICBpZihpIT0xNikKICB7CiAgICBjb3VudC50YWI8LWNvdW50cy5oeXBveGlhWyxjKDE6MywoKDMqaSktMik6KDMqaSkpXQogICAgI1JvdW5kIHJlYWQgY291bnRzIChkZXRlcm1pbmVkIGJ5IER1ZmZ5TkdTKQogICAgY291bnQudGFiPC1yb3VuZChjb3VudC50YWIpCiAgICAjTWV0YWRhdGEgKHRyaXBsaWNhdGVzKQogICAgZXhwLm1ldGFkYXRhIDwtIGRhdGEuZnJhbWUoY29uZGl0aW9uID0gZmFjdG9yKHJlcChjKCJjb250cm9sIiwgInRwb2ludCIpLCBjKDMsMykpKSkKICAgIHJvd25hbWVzKGV4cC5tZXRhZGF0YSkgPC0gY29sbmFtZXMoY291bnQudGFiKQogICAgZGRzIDwtIERFU2VxRGF0YVNldEZyb21NYXRyaXgoY291bnQudGFiLCBleHAubWV0YWRhdGEsIH5jb25kaXRpb24pCiAgICAjUmVtb3ZlIGdlbmVzIHdpdGggemVybyByZWFkcyBpbiBhbGwgc2FtcGxlcwogICAgZGRzIDwtIGRkc1sgcm93U3Vtcyhjb3VudHMoZGRzKSkgPiAxLCBdCiAgICAjUnVuIG1haW4gZnVuY3Rpb24KICAgIGRkcyA8LSBERVNlcShkZHMpCiAgICAjT3V0cHV0CiAgICBvdXRwdXQuZGVzZXEgPC0gcmVzdWx0cyhkZHMpCiAgICAjU2F2ZSBuYW1lcyBvZiBERUdzCiAgICBkZWdzPC1yb3duYW1lcyhvdXRwdXQuZGVzZXEpW3doaWNoKG91dHB1dC5kZXNlcSRwYWRqIDwgMC4wNSAmIGFicyhvdXRwdXQuZGVzZXEkbG9nMkZvbGRDaGFuZ2UpPjEpXQogICAgZGUuZ2VuZXNbW2ldXTwtZGVncwogICAgREVHcy50aW1lY291cnNlPC11bmlvbihERUdzLnRpbWVjb3Vyc2UsZGVncykKICB9CiAgI0ZvciBUMTUKICBlbHNlCiAgewogICAgY291bnQudGFiPC1jb3VudHMuaHlwb3hpYVssYygxOjMsKCgzKmkpLTIpOigoMyppKSszKSldCiAgICAjUm91bmQgcmVhZCBjb3VudHMKICAgIGNvdW50LnRhYjwtcm91bmQoY291bnQudGFiKQogICAgI01ldGFkYXRhCiAgICBleHAubWV0YWRhdGEgPC0gZGF0YS5mcmFtZShjb25kaXRpb24gPSBmYWN0b3IocmVwKGMoImNvbnRyb2wiLCAidHBvaW50IiksIGMoMyw2KSkpKQogICAgcm93bmFtZXMoZXhwLm1ldGFkYXRhKSA8LSBjb2xuYW1lcyhjb3VudC50YWIpCiAgICBkZHMgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeChjb3VudC50YWIsIGV4cC5tZXRhZGF0YSwgfmNvbmRpdGlvbikKICAgICNSZW1vdmUgZ2VuZXMgd2l0aCB6ZXJvIHJlYWRzIGluIGFsbCBzYW1wbGVzCiAgICBkZHMgPC0gZGRzWyByb3dTdW1zKGNvdW50cyhkZHMpKSA+IDEsIF0KICAgICNSdW4gbWFpbiBmdW5jdGlvbgogICAgZGRzIDwtIERFU2VxKGRkcykKICAgICNPdXRwdXQKICAgIG91dHB1dC5kZXNlcSA8LSByZXN1bHRzKGRkcykKICAgICNTYXZlIG5hbWVzIG9mIERFR3MKICAgIGRlZ3M8LXJvd25hbWVzKG91dHB1dC5kZXNlcSlbd2hpY2gob3V0cHV0LmRlc2VxJHBhZGogPCAwLjA1ICYgYWJzKG91dHB1dC5kZXNlcSRsb2cyRm9sZENoYW5nZSk+MSldCiAgICBkZS5nZW5lc1tbaV1dPC1kZWdzCiAgICBERUdzLnRpbWVjb3Vyc2U8LXVuaW9uKERFR3MudGltZWNvdXJzZSxkZWdzKQogIH0KfQpgYGAKMmEuIE5vcm1hbGl6ZWQgZnVsbCBleHByZXNzaW9uIG1hdHJpeCBhbmQgcGVyZm9ybSBoaWVyYXJjaGljaGFsIGNsdXN0ZXJpbmcgb24gbWVkaWFuIHByb2ZpbGVzCmBgYHtyfQojTm9ybWFsaXplIHJlYWQgY291bnRzIHVzaW5nIGFsbCB0aW1lIHBvaW50cyBhbmQgcmVwbGljYXRlcwpjb3VudC50YWI8LXJvdW5kKGNvdW50cy5oeXBveGlhKQojTWV0YWRhdGEKZXhwLm1ldGFkYXRhIDwtIGRhdGEuZnJhbWUoY29uZGl0aW9uID0gZmFjdG9yKHNhcHBseSgxOm5jb2woY291bnRzLmh5cG94aWEpLGZ1bmN0aW9uKHgpe3N0cnNwbGl0KGNvbG5hbWVzKGNvdW50cy5oeXBveGlhKVt4XSxzcGxpdCA9ICJfIilbWzFdXVsxXX0pKSkKcm93bmFtZXMoZXhwLm1ldGFkYXRhKSA8LSBjb2xuYW1lcyhjb3VudC50YWIpCiNFc3RpbWF0ZSBub3JtYWxpemVkIGNvdW50cyB3aXRoIERFU2VxMiAobG9nIHRyYW5zZm9ybWF0aW9uIGFuZCBub3JtYWwgZGlzdHJpYnV0aW9uKQpkZHMgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeChjb3VudC50YWIsIGV4cC5tZXRhZGF0YSwgfmNvbmRpdGlvbikKZGRzIDwtIGVzdGltYXRlU2l6ZUZhY3RvcnMoZGRzKQpub3JtYWxpemVkLmNvdW50cyA8LSBhc3NheShub3JtVHJhbnNmb3JtKGRkcykpCiNFc3RpbWF0ZSBtZWRpYW4gY291bnRzIGZvciBlYWNoIGdlbmUgcGVyIHRpbWUgcG9pbnQKdGltZS5wb2ludHM8LXBhc3RlKHVuaXF1ZShleHAubWV0YWRhdGEkY29uZGl0aW9uKSwiXyIsc2VwPSIiKQptZWRpYW4ubm9ybWFsaXplZC5jb3VudHM8LWMoKQpmb3IoeiBpbiB0aW1lLnBvaW50cykKewoJY29sLnBvczwtZ3JlcCh6LGNvbG5hbWVzKG5vcm1hbGl6ZWQuY291bnRzKSkKCW1lZGlhbi5ub3JtYWxpemVkLmNvdW50czwtY2JpbmQobWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzLGFwcGx5KEZVTj1tZWRpYW4sTUFSR0lOPTEsbm9ybWFsaXplZC5jb3VudHNbLGNvbC5wb3NdKSkKfQpjb2xuYW1lcyhtZWRpYW4ubm9ybWFsaXplZC5jb3VudHMpPC1wYXN0ZSgiVCIsMDoyNSxzZXA9IiIpCiNIaWVyYXJjaGljYWwgY2x1c3RlcmluZyB3aXRoIGJvb3RzdHJhcCB2YWx1ZXMKaGllcmFyY2hpY2FsLmNsdXN0ZXJpbmcuZXVjbGlkZWFuPC1wdmNsdXN0KGRhdGEgPSBtZWRpYW4ubm9ybWFsaXplZC5jb3VudHMsbWV0aG9kLmRpc3QgPSJldWNsaWRlYW4iKQpwbG90KGhpZXJhcmNoaWNhbC5jbHVzdGVyaW5nLmV1Y2xpZGVhbiwgcHJpbnQucHY9VFJVRSwgcHJpbnQubnVtPUZBTFNFLCBmbG9hdD0wLjAxLAogICAgIGNvbC5wdj1jKDIsMyw4KSwgY2V4LnB2PTAuOCxjZXg9MC44LG1haW49IkZpZyBTMUIiKQpgYGAKMmIuIEFwcGx5IFBDQSB0byB0aGUgZnVsbCBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4CmBgYHtyfQojVGhlIGF2ZXJhZ2Ugb2YgdGhlIHR3byBzYW1wbGVzIGZyb20gZWFjaCByZWFjdG9yIHdhcyB1c2VkIGZvciBkZWZpbmluZyBUMTUKbm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2U8LWNiaW5kKG5vcm1hbGl6ZWQuY291bnRzWywxOjQ1XSxyb3dNZWFucyhub3JtYWxpemVkLmNvdW50c1ssYyg0Niw0OSldKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd01lYW5zKG5vcm1hbGl6ZWQuY291bnRzWyxjKDQ3LDUwKV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93TWVhbnMobm9ybWFsaXplZC5jb3VudHNbLGMoNDgsNTEpXSksIG5vcm1hbGl6ZWQuY291bnRzWyw1Mjo4MV0pCmNvbG5hbWVzKG5vcm1hbGl6ZWQuY291bnRzLnQxNV9hdmVyYWdlKVs0Njo0OF08LWMoIlQxNV9BIiwiVDE1X0IiLCJUMTVfQyIpCiNSdW4gUENBCnBjYS5pbnB1dDwtdChub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSkKcGNhLm91dHB1dDwtcHJjb21wKHBjYS5pbnB1dCkKI1Bsb3QgM0QgUENBIChmb3IgdGhlIGZpbmFsIEZpZ3VyZSBTMUEgd2UgdXNlZCB0aGUgaW50ZXJhY3RpdmUgcGxvdDNkIGZ1bmN0aW9uKQojY2x1c3RlcnMuY29sb3JzPC1jKCJibHVlIiwiZGVlcHNreWJsdWUyIiwib3JhbmdlIiwiZGFya29saXZlZ3JlZW4xIiwiZ3JlZW4zIiwicmVkIikKI3N0YXRlTGFiZWxzPWMocmVwKDEsMikscmVwKDIsNiksMywzLDQsMyxyZXAoNCw0KSxyZXAoNSwzKSxyZXAoMywzKSxyZXAoNiw0KSkKI2dyIDwtIGdyaWQzZCgneicpCiNwbG90M2QocGNhLm91dHB1dCR4WywxOjNdLGNvbD1yZXAoY2x1c3RlcnMuY29sb3JzW3N0YXRlTGFiZWxzXSxlYWNoPTMpLHNpemUgPSAxMCkKI2F4ZXMzZChsYWJlbHM9Rix0aWNrPUYsYm94PUYpCiN0ZXh0M2QocGNhLm91dHB1dCR4WywxOjNdKzEsdGV4dHM9cm93bmFtZXMocGNhLmlucHV0KSxjZXg9MS4xKQpwY2EucGxvdDwtc2NhdHRlcnBsb3QzZCh4ID0gcGNhLm91dHB1dCR4WywxXSwgeSA9IHBjYS5vdXRwdXQkeFssM10sIHogPSBwY2Eub3V0cHV0JHhbLDJdLAogICAgICAgICAgICAgICBwY2ggPSAxOSxhbmdsZSA9IC0xOTAseGxhYiA9ICJQQzEiLHlsYWI9IlBDMyIsemxhYj0iUEMyIixjb2xvcj1yZXAodmlyaWRpcygyNiksZWFjaD0zKSxtYWluPSJGaWcgUzFBIikKbGVnZW5kKHBjYS5wbG90JHh5ei5jb252ZXJ0KDIwLCAtNDAsIDMwKSwgcGNoPTE5LCBjb2w9dmlyaWRpcygyNiksIGxlZ2VuZCA9IHVuaXF1ZShleHAubWV0YWRhdGEkY29uZGl0aW9uKSwgYnR5PSJvIiwgY2V4PS4zNSkKYGBgCjJjLiBBcHBseSBjbHVzdGVyaW5nIGFsZ29yaXRobXMgKGhpZXJhcmNoaWNoYWwgY2x1c3RlcmluZywgay1tZWFucykgdG8gdGhlIGZ1bGwgbm9ybWFsaXplZCBleHByZXNzaW9uIG1hdHJpeApgYGB7cn0KI0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHdpdGggYm9vdHN0cmFwIHZhbHVlcyAoYWxsIHJlcGxpY2F0ZXMpCmhpZXJhcmNoaWNhbC5jbHVzdGVyaW5nLmV1Y2xpZGVhbi5yZXBzPC1wdmNsdXN0KGRhdGEgPSBub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSxtZXRob2QuZGlzdCA9ImV1Y2xpZGVhbiIpCnBsb3QoaGllcmFyY2hpY2FsLmNsdXN0ZXJpbmcuZXVjbGlkZWFuLnJlcHMsIHByaW50LnB2PVRSVUUsIHByaW50Lm51bT1GQUxTRSwgZmxvYXQ9MC4wMSwKICAgICBjb2wucHY9YygyLDMsOCksIGNleC5wdj0wLjQsY2V4PTAuNSxtYWluPSJGaWcgUzFDIikKI0stbWVhbnMgd2l0aCBrPTIKay5tZWFucy4yPC1rbWVhbnModChub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSksMixuc3RhcnQgPSAyNSkKI1ByaW50IG9uZSBvZiB0aGUgdHdvIGstbWVhbnMgY2x1c3RlcnMKcHJpbnQobmFtZXMod2hpY2goay5tZWFucy4yJGNsdXN0ZXIgPT0gMSkpKQojSy1tZWFucyB3aXRoIGs9NgprLm1lYW5zLjY8LWttZWFucyh0KG5vcm1hbGl6ZWQuY291bnRzLnQxNV9hdmVyYWdlKSw2LG5zdGFydCA9IDI1KQojUHJpbnQgcmVwbGljYXRlcyBjbHVzdGVycyAocmVsYXRlZCB0byBGaWcgUzFEKQpwcmludChrLm1lYW5zLjYkY2x1c3RlcikKI0NvbXB1dGUgc2lsaG91ZXR0ZSBjbHVzdGVyaW5nIG1ldHJpYyB1c2luZyBjbFZhbGlkIHBhY2thZ2UgCmNsVmFsaWQuaW50ZXJuYWwubWV0cmljcyA8LSBjbFZhbGlkKHQobm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2UpLCBuQ2x1c3QgPSAyOjI2LGNsTWV0aG9kcyA9IGMoImhpZXJhcmNoaWNhbCIsImttZWFucyIsImRpYW5hIiwicGFtIiwiY2xhcmEiKSwgdmFsaWRhdGlvbiA9ICJpbnRlcm5hbCIpCiNTaWxob3VldHRlIHZhbHVlcyB3ZXJlIGV4dHJhY3RlZCB1c2luZyB0aGUgc3VtbWFyeShjbFZhbGlkLmludGVybmFsLm1ldHJpY3MpIGNvbW1hbmQKc2lsaG91ZXR0ZS5rbWVhbnM8LWMoMCwwLjQ3MDYsMC4yOTQ4LDAuMzA0MywwLjM2NjYsMC4zODE0LDAuMzU1MywwLjM0MzIsMC4zMTYzLDAuMzEzNiwwLjI5NDksMC4yOTAyLCAgIAogICAgICAgICAgICAgIDAuMjcxMCwwLjI1NTUsMC4yNTA2LDAuMjQ5OCwwLjIzMDMsMC4yMzE3LDAuMjI4MywwLjIyNzcsMC4yMzEwLDAuMjI3NSwwLjIyNTAsCiAgICAgICAgICAgICAgMC4yMjE2LDAuMjIwMCwwLjIxNTUpCnBsb3QoeD0xOjI2LHk9c2lsaG91ZXR0ZS5rbWVhbnMsbHdkPTEuMix0eXBlPSJvIixwY2g9MjAseGxhYj0iTnVtYmVyIG9mIGNsdXN0ZXJzIGsiLHlsYWI9IkF2ZXJhZ2Ugc2lsaG91ZXR0ZSB3aWR0aCIsY2V4LmxhYj0xLjUsY2V4PTEuNSxjb2w9InB1cnBsZSIsY2V4LmF4aXM9MS41LCBtYWluPSJGaWcgUzFFIikKYGBgCjJkLiBFbnNlbWJsZSBjbHVzdGVyaW5nLSBQYXJ0IEkKYGBge3J9CiNGaXJzdCB3ZSBwZXJmb3JtIGVuc2VtYmxlIGNsdXN0ZXJpbmcgZm9yIGstbWVhbnMgKHdpdGggMTwgayA8IDExKQojTnVtYmVyIG9mIGJvb3RzdHJhcHMKTj01MDAwCiNDcmVhdGUgY28tY2x1c3RlcmluZyBtYXRyaXgKY2x1c3RlcmluZy5tYXRyaXg8LW1hdHJpeChuY29sPTI2LG5yb3c9MjYsMCkKcm93bmFtZXMoY2x1c3RlcmluZy5tYXRyaXgpPC11bmlxdWUoc2FwcGx5KDE6bmNvbChub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSksZnVuY3Rpb24oeCl7c3Ryc3BsaXQoY29sbmFtZXMobm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2UpW3hdLHNwbGl0ID0gIl8iKVtbMV1dWzFdfSkpCmNvbG5hbWVzKGNsdXN0ZXJpbmcubWF0cml4KTwtcm93bmFtZXMoY2x1c3RlcmluZy5tYXRyaXgpCmZvcihuIGluIDE6TikKewogICNOZXcgdGltZSBzZXJpZXMgd2l0aCByZXBsaWNhdGVzIGZyb20gZGlmZmVyZW50IHJlYWN0b3JzIChvbmUgcmFuZG9tIHJlcGxpY2F0ZSBwZXIgdGltZSBwb2ludCkKICBwZXJtdXRhdGlvbi5zYW1wbGluZzwtYygpCiAgZm9yKGkgaW4gMDoyNSkKICB7CiAgI0xvY2F0aW9uIG9mIGN1cnJlbnQgdGltZSBwb2ludCByZXBsaWNhdGVzIGluIHRoZSBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4CiAgcG90ZW50aWFsLnBvaW50PC0gKGkqMykgKyAoMTozKQogICNSYW5kb20gc2VsZWN0aW9uIG9mIHNpbmdsZSByZXBsaWNhdGUKICBzZWxlY3RlZC5wb2ludDwtcG90ZW50aWFsLnBvaW50W3NhbXBsZSgxOjMsMSldCiAgI0FkZCBzZWxlY3RlZCByZXBsaWNhdGUgdG8gdGltZSBzZXJpZXMKICBwZXJtdXRhdGlvbi5zYW1wbGluZzwtY2JpbmQocGVybXV0YXRpb24uc2FtcGxpbmcsbm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2VbLHNlbGVjdGVkLnBvaW50XSkKICB9CiAgY29sbmFtZXMocGVybXV0YXRpb24uc2FtcGxpbmcpPC1jb2xuYW1lcyhjbHVzdGVyaW5nLm1hdHJpeCkKICAjQ29tcHV0ZSBrLW1lYW5zIHdpdGggayBpbiB0aGUgWzIsMTBdIHJhbmdlCiAgZm9yKHogaW4gMjoxMCkKICB7CiAgdGVtcC5rbWVhbnMuejwta21lYW5zKHQocGVybXV0YXRpb24uc2FtcGxpbmcpLHosbnN0YXJ0ID0gMjUpCiAgI0lkZW50aWZ5IHRpbWUgcG9pbnRzIHRoYXQgY2x1c3RlcmVkIHRvZ2V0aGVyCiAgZm9yKGEgaW4gMTp6KXsKICBjbHVzdGVyZWQucG9pbnRzPC1uYW1lcyh3aGljaCggdGVtcC5rbWVhbnMueiRjbHVzdGVyID09IGEpKQogIGlmKGxlbmd0aChjbHVzdGVyZWQucG9pbnRzKT4xKQogIHsKICBmb3IoZCBpbiAxOmxlbmd0aChjbHVzdGVyZWQucG9pbnRzKSkKICB7CiAgICBmb3IoYyBpbiAxOmxlbmd0aChjbHVzdGVyZWQucG9pbnRzKSkKICAgIHsKICAgICAgY2x1c3RlcmluZy5tYXRyaXhbY2x1c3RlcmVkLnBvaW50c1tkXSxjbHVzdGVyZWQucG9pbnRzW2NdXTwtIGNsdXN0ZXJpbmcubWF0cml4W2NsdXN0ZXJlZC5wb2ludHNbZF0sY2x1c3RlcmVkLnBvaW50c1tjXV0rMQogICAgfQogIH0KIH0KIGVsc2UKIHsKICBjbHVzdGVyaW5nLm1hdHJpeFtjbHVzdGVyZWQucG9pbnRzLGNsdXN0ZXJlZC5wb2ludHNdPC0gY2x1c3RlcmluZy5tYXRyaXhbY2x1c3RlcmVkLnBvaW50cyxjbHVzdGVyZWQucG9pbnRzXSsxCiB9CiAJCQl9Cn0KCn0KaGVhdG1hcC4yKGNsdXN0ZXJpbmcubWF0cml4LyhOKjkpLFJvd3YgPSAiTkEiLGRlbmRyb2dyYW0gPSAibm9uZSIsQ29sdj0iTkEiLHRyYWNlID0gIm5vbmUiLGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwoMjAsICJQaVlHIikpICkoMjApLGRlbnNpdHkuaW5mbz0ibm9uZSIsbWFpbj0iRmlnIFMxRiIpCmBgYAoKMmUuIEVuc2VtYmxlIGNsdXN0ZXJpbmctIFBhcnQgSUkKYGBge3J9CiNOb3cgdGhhdCB0aGUgZXhpc3RlbmNlIG9mIHNpeCBjbHVzdGVycyBoYXMgYmVlbiBjb25maXJtZWQuIFdlIHBlcmZvcm0gZW5zZW1ibGUgY2x1c3RlcmluZyBmb3Igay1tZWFucyAoaz02KQojTnVtYmVyIG9mIGJvb3RzdHJhcHMKTj0xMDAwMAojQ3JlYXRlIGNvLWNsdXN0ZXJpbmcgbWF0cml4CmNsdXN0ZXJpbmcubWF0cml4LjIwMDwtbWF0cml4KG5jb2w9MjYsbnJvdz0yNiwwKQpyb3duYW1lcyhjbHVzdGVyaW5nLm1hdHJpeC4yMDApPC11bmlxdWUoc2FwcGx5KDE6bmNvbChub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSksZnVuY3Rpb24oeCl7c3Ryc3BsaXQoY29sbmFtZXMobm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2UpW3hdLHNwbGl0ID0gIl8iKVtbMV1dWzFdfSkpCmNvbG5hbWVzKGNsdXN0ZXJpbmcubWF0cml4LjIwMCk8LXJvd25hbWVzKGNsdXN0ZXJpbmcubWF0cml4LjIwMCkKZm9yKG4gaW4gMTpOKQp7CiAgI05ldyB0aW1lIHNlcmllcyB3aXRoIHJlcGxpY2F0ZXMgZnJvbSBkaWZmZXJlbnQgcmVhY3RvcnMgKG9uZSByYW5kb20gcmVwbGljYXRlIHBlciB0aW1lIHBvaW50KQogIHBlcm11dGF0aW9uLnNhbXBsaW5nPC1jKCkKICBmb3IoaSBpbiAwOjI1KQogIHsKICAjTG9jYXRpb24gb2YgY3VycmVudCB0aW1lIHBvaW50IHJlcGxpY2F0ZXMgaW4gdGhlIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXgKICBwb3RlbnRpYWwucG9pbnQ8LSAoaSozKSArICgxOjMpCiAgI1JhbmRvbSBzZWxlY3Rpb24gb2Ygc2luZ2xlIHJlcGxpY2F0ZQogIHNlbGVjdGVkLnBvaW50PC1wb3RlbnRpYWwucG9pbnRbc2FtcGxlKDE6MywxKV0KICAjQWRkIHNlbGVjdGVkIHJlcGxpY2F0ZSB0byB0aW1lIHNlcmllcwogIHBlcm11dGF0aW9uLnNhbXBsaW5nPC1jYmluZChwZXJtdXRhdGlvbi5zYW1wbGluZyxub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZVssc2VsZWN0ZWQucG9pbnRdKQogIH0KICBjb2xuYW1lcyhwZXJtdXRhdGlvbi5zYW1wbGluZyk8LWNvbG5hbWVzKGNsdXN0ZXJpbmcubWF0cml4LjIwMCkKICAjUnVuIGstbWVhbnMgd2l0aCBrPTYKICB0ZW1wLmttZWFucy42PC1rbWVhbnModChwZXJtdXRhdGlvbi5zYW1wbGluZyksNixuc3RhcnQgPSAyNSkKICAjSWRlbnRpZnkgdGltZSBwb2ludHMgdGhhdCBjbHVzdGVyZWQgdG9nZXRoZXIKICBmb3IoeiBpbiAxOjYpCiAgewogIGNsdXN0ZXJlZC5wb2ludHM8LW5hbWVzKHdoaWNoKHRlbXAua21lYW5zLjYkY2x1c3RlciA9PSB6KSkKICBpZihsZW5ndGgoY2x1c3RlcmVkLnBvaW50cyk+MSkKICB7CiAgZm9yKGQgaW4gMTpsZW5ndGgoY2x1c3RlcmVkLnBvaW50cykpCiAgewogICAgZm9yKGMgaW4gMTpsZW5ndGgoY2x1c3RlcmVkLnBvaW50cykpCiAgICB7CiAgICAgIGNsdXN0ZXJpbmcubWF0cml4LjIwMFtjbHVzdGVyZWQucG9pbnRzW2RdLGNsdXN0ZXJlZC5wb2ludHNbY11dPC0gY2x1c3RlcmluZy5tYXRyaXguMjAwW2NsdXN0ZXJlZC5wb2ludHNbZF0sY2x1c3RlcmVkLnBvaW50c1tjXV0rMQogICAgfQogIH0KIH0KIGVsc2UKIHsKICBjbHVzdGVyaW5nLm1hdHJpeC4yMDBbY2x1c3RlcmVkLnBvaW50cyxjbHVzdGVyZWQucG9pbnRzXTwtIGNsdXN0ZXJpbmcubWF0cml4LjIwMFtjbHVzdGVyZWQucG9pbnRzLGNsdXN0ZXJlZC5wb2ludHNdKzEKIH0KfQoKfQpjbHVzdGVyaW5nLmVuc2VtYmxlLjIwMGs8LWNsdXN0ZXJpbmcubWF0cml4LjIwMC9OIApoZWF0bWFwLjIoY2x1c3RlcmluZy5lbnNlbWJsZS4yMDBrLFJvd3YgPSAiTkEiLGRlbmRyb2dyYW0gPSAibm9uZSIsQ29sdj0iTkEiLHRyYWNlID0gIm5vbmUiLGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUocmV2KGJyZXdlci5wYWwoMjAsICJQaVlHIikpICkoMjApLGRlbnNpdHkuaW5mbz0ibm9uZSIsbWFpbj0iRmlnIFMxRyIpCmBgYAoyZi4gQ29tcGFyZSBpbnRlci0gYW5kIGludHJhLWdyb3VwIHZhcmlhdGlvbiB1c2luZyBxdWFudHJvIChvcmlnaW5hbCBydW4gaW4gUi0zLjYpCmBgYHtyfQojcXVhbnRyby5zdGF0ZUxhYmVscz1jKHJlcCgiTiIsNikscmVwKCJEIiwxOCkscmVwKCJMIiw2KSxyZXAoIkUiLDMpLHJlcCgiTCIsMykscmVwKCJFIiwxMikscmVwKCJNIiw5KSxyZXAoIkwiLDkpLHJlcCgiUiIsMTIpKQojUmVtb3ZlIGdlbmVzIHdpdGggemVybyBjb3VudHMgaW4gYWxsIHJlcGxpY2F0ZXMKI3plcm8ucm93czwtd2hpY2goYXBwbHkoRlVOPW1heCxNQVJHSU49MSxub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSk9PTApCiNub3JtYWxpemVkLmNvdW50cy5yZXBzPC1ub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZVstMSp6ZXJvLnJvd3MsXQojUnVuIFF1YW50cm8gd2l0aCAxMDAwMCBib290c3RyYXBzCiNxdGVzdC5ub3JtYWxpemVkIDwtIHF1YW50cm8ob2JqZWN0ID0gbm9ybWFsaXplZC5jb3VudHMucmVwcywgZ3JvdXBGYWN0b3IgPSBxdWFudHJvLnN0YXRlTGFiZWxzLEIgPSAxMDAwMCkKI1Bsb3QgcmVzdWx0CiNxdWFudHJvUGxvdChxdGVzdC5ub3JtYWxpemVkKQpgYGAKCjJnLiBWaXN1YWxpemUgY2x1c3RlcnMgKHN0YXRlcykgd2l0aCB0U05FIHBsb3QKYGBge3IgbWVzc2FnZT1GQUxTRX0KI1J1biB0U05FICh1c2luZyBvbmx5IERFR3MgdG8gaGF2ZSBiZXN0IHJlc29sdXRpb24pCmNsdXN0ZXJzLmNvbG9yczwtYygiYmx1ZSIsImRlZXBza3libHVlMiIsIm9yYW5nZSIsImRhcmtvbGl2ZWdyZWVuMSIsImdyZWVuMyIsInJlZCIpCnN0YXRlTGFiZWxzPWMocmVwKDEsMikscmVwKDIsNiksMywzLDQsMyxyZXAoNCw0KSxyZXAoNSwzKSxyZXAoMywzKSxyZXAoNiw0KSkKdHNuZS5pbnB1dDwtdChtZWRpYW4ubm9ybWFsaXplZC5jb3VudHNbREVHcy50aW1lY291cnNlLF0pCnRzbmUub3V0cHV0PVJ0c25lKHRzbmUuaW5wdXQsZGltcz0yLHBlcnBsZXhpdHk9Nyx2ZXJib3NlPVRSVUUpCnBsb3QodHNuZS5vdXRwdXQkWSxjb2w9Y2x1c3RlcnMuY29sb3JzW3N0YXRlTGFiZWxzXSxwY2g9MTkseGxhYj0ndFNORSBDb21wb25lbnQgMScseWxhYj0ndFNORSBDb21wb25lbnQgMicsbWFpbj0iRmlnIDJBIikKdGV4dCh4PXRzbmUub3V0cHV0JFlbLDFdLHk9dHNuZS5vdXRwdXQkWVssMl0scm93bmFtZXModHNuZS5pbnB1dCkpCmBgYAozYS4gRGVmaW5lIGdlbmUgY2x1c3RlcnMgYmFzZWQgb24gY2x1c3RlcnMtYXZlcmFnZSBleHByZXNzaW9uIHByb2ZpbGUKYGBge3J9CiNUaGUgc2l4IHN0YXRlcyBkZWZpbmVkIGluIEZpZyAyQQp0cmFuc2NyaXB0aW9uYWwuc3RhdGVzPC1jKCJOb3Jtb3hpYSIsIkRlcGxldGlvbiIsIkVhcmx5IiwiTWlkIiwiTGF0ZSIsIlJlc3VzY2l0YXRpb24iKQojVGltZSBwb2ludHMtc3RhdGVzIGFzc29jaWF0aW9uCnRpbWUucG9pbnRzLmNsdXN0ZXJzPC1saXN0KGMoMSwyKSwzOjgsYygxMSwxMzoxNiksMTc6MTksYyg5LDEwLDEyLDIwOjIyKSwyMzoyNikKbmFtZXModGltZS5wb2ludHMuY2x1c3RlcnMpPC10cmFuc2NyaXB0aW9uYWwuc3RhdGVzCmdlbmUuY2x1c3RlcnM8LXJlcChsaXN0KE5VTEwpLGxlbmd0aCh0cmFuc2NyaXB0aW9uYWwuc3RhdGVzKSkKI0Fzc2lnbiBlYWNoIERFRyB0byBvbmUgdHJhbnNjcmlwdGlvbmFsIHN0YXRlCmZvcihmIGluIERFR3MudGltZWNvdXJzZSkKewoJI0luaXRpYWxpemUgbWF4aW11bSBhdmVyYWdlIGV4cHJlc3Npb24gYW5kIHN0YXRlIG1lbWJlcnNoaXAKICBtYXgubGV2ZWw8LW1lYW4obWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzW2YsdGltZS5wb2ludHMuY2x1c3RlcnNbWzFdXV0pCglnZW5lLnN0YXRlPC0xCgkjRXZhbHVhdGUgaWYgdGhlIGF2ZXJhZ2UgaXMgaGlnaGVyIGluIGFueSBvZiB0aGUgb3RoZXIgc3RhdGVzCglmb3IgKHMgaW4gMjo2KQoJewoJICB0ZW1wLm1lYW48LW1lYW4obWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzW2YsdGltZS5wb2ludHMuY2x1c3RlcnNbW3NdXV0pCgkJI1VwZGF0ZSBzdGF0ZSBhbmQgbWF4aW11bSBhdmVyYWdlCgkgIGlmICh0ZW1wLm1lYW4gPiBtYXgubGV2ZWwpCgkJewoJCQlnZW5lLnN0YXRlIDwtIHMKCQkJbWF4LmxldmVsPC10ZW1wLm1lYW4KCQl9Cgl9CQoJZ2VuZS5jbHVzdGVyc1tbZ2VuZS5zdGF0ZV1dPC1jKGdlbmUuY2x1c3RlcnNbW2dlbmUuc3RhdGVdXSxmKQp9Cm5hbWVzKGdlbmUuY2x1c3RlcnMpPC10cmFuc2NyaXB0aW9uYWwuc3RhdGVzCiNUaGlzIGZ1bmN0aW9uIGNvbXB1dGVzIGJpY2x1c3RlciByZXNpZHVhbCB1c2luZyBmb3JtdWxhIGZyb20gQ2hlbmcgYW5kIENodXJjaCwgMjAwMApiYy5yZXNpZHVhbDwtZnVuY3Rpb24oZ2VuZXMsY29uZGl0aW9ucyxleHAubWF0KQp7Cgl2b2x1bWU8LWxlbmd0aChnZW5lcykqbGVuZ3RoKGNvbmRpdGlvbnMpCglEX0lKPC1tZWFuKGV4cC5tYXRbZ2VuZXMsY29uZGl0aW9uc10pCglyZXNpZHVhbDwtMAoJZm9yKGYgaW4gZ2VuZXMpCgl7CgkJZm9yKHogaW4gY29uZGl0aW9ucykKCQl7CgkJCXRlbXAucjwtZXhwLm1hdFtmLHpdIC0gbWVhbihleHAubWF0W2YsY29uZGl0aW9uc10pIC0gbWVhbihleHAubWF0W2dlbmVzLHpdKSArIERfSUoKCQkJcmVzaWR1YWw8LSByZXNpZHVhbCArICh0ZW1wLnIpXjIKCQl9Cgl9CglyZXNpZHVhbDwtIHJlc2lkdWFsL3ZvbHVtZQoJcmVzaWR1YWwKfQojVGhpcyBmdW5jdGlvbiBjb21wdXRlcyB0aGUgYXZlcmFnZSBQZWFyc29uIGNvcnJlbGF0aW9uIG9mIGEgZ3JvdXAgb2YgZ2VuZXMKY2x1c3Rlci5jb3JyZWxhdGlvbjwtZnVuY3Rpb24oZ2VuZXMpCnsKICBhbGwuY29ycmVsYXRpb25zPC1jKCkKICBmb3IodyBpbiAxOihsZW5ndGgoZ2VuZXMpLTEpKQogIHsKICAgIGZvcih0IGluICh3KzEpOmxlbmd0aChnZW5lcykpCiAgICB7CiAgICAgIGFsbC5jb3JyZWxhdGlvbnM8LWMoYWxsLmNvcnJlbGF0aW9ucyxjb3IobWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzW2dlbmVzW3ddLF0sbWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzW2dlbmVzW3RdLF0pKQogICAgfQogIH0KICBvdXRwdXQ8LW1lYW4oYWxsLmNvcnJlbGF0aW9ucykKICBvdXRwdXQKfQpgYGAKM2IuIENvbXBhcmlzb24gd2l0aCBCb3J1dGEgY2x1c3RlcmluZyAocmFuZG9tIGZvcmVzdCBiYXNlZCBhcHByb2FjaCkKYGBge3IgbWVzc2FnZT1GQUxTRX0KI0VhY2ggc3RhdGUgdnMgdGhlIG90aGVyIGZpdmUgc3RhdGVzCmJlc3RQYXJ0aXRpb248LWMoMCwwLDEsMSwxLDEsMSwxLDQsNCwyLDQsMiwyLDIsMiwzLDMsMyw0LDQsNCw1LDUsNSw1KQpib3J1dGEuc2lnbmF0dXJlLmdlbmVzPC1saXN0KCkKZm9yKHogaW4gMDo1KQp7Cmh5cG94aWEuZGY8LWFzLmRhdGEuZnJhbWUoY2JpbmQoYmVzdFBhcnRpdGlvbix0KG1lZGlhbi5ub3JtYWxpemVkLmNvdW50c1tERUdzLnRpbWVjb3Vyc2UsXSkpKQpoeXBveGlhLmRmJGJlc3RQYXJ0aXRpb25bd2hpY2goaHlwb3hpYS5kZiRiZXN0UGFydGl0aW9uICE9IHopXTwtIDEwMApoeXBveGlhLmRmJGJlc3RQYXJ0aXRpb248LWFzLmZhY3RvcihoeXBveGlhLmRmJGJlc3RQYXJ0aXRpb24pCnNldC5zZWVkKDEyMykKYm9ydXRhLnRyYWluIDwtIEJvcnV0YShiZXN0UGFydGl0aW9ufi4sIGRhdGEgPSBoeXBveGlhLmRmLCBkb1RyYWNlID0gMikKZmluYWwuYm9ydXRhIDwtIFRlbnRhdGl2ZVJvdWdoRml4KGJvcnV0YS50cmFpbikKcHJpbnQoZmluYWwuYm9ydXRhKQpib3J1dGEuc2lnbmF0dXJlLmdlbmVzW1t6KzFdXTwtbmFtZXMoZmluYWwuYm9ydXRhJGZpbmFsRGVjaXNpb25bd2hpY2goZmluYWwuYm9ydXRhJGZpbmFsRGVjaXNpb24gPT0gIkNvbmZpcm1lZCIpXSkKfQpuYW1lcyhib3J1dGEuc2lnbmF0dXJlLmdlbmVzKTwtYygiTm9ybW94aWEiLCJEZXBsZXRpb24iLCJFYXJseSIsIk1pZCIsIkxhdGUiLCJSZXN1c2NpdGF0aW9uIikKI05vdyB3ZSBjcmVhdGUgVGFibGUgUzMKdGFibGVTMzwtbWF0cml4KG5yb3c9bGVuZ3RoKGdlbmUuY2x1c3RlcnMpLG5jb2w9NixOQSxkaW1uYW1lcyA9IGxpc3QodHJhbnNjcmlwdGlvbmFsLnN0YXRlcyxjKCJHZW5lcyBzZWxlY3RlZCBiYXNlZCBvbiBtZWFuIiwgIk1lYW4gU3F1YXJlIFJlc2lkdWFsIiwiUGVhcnNvbiBjb3JyZWxhdGlvbiIsICIjIEJvcnV0YSBnZW5lcyIsIk92ZXJsYXAiLCAiUmVjYWxsLUJvcnV0YSIpKSkKZm9yKHogaW4gcm93bmFtZXModGFibGVTMykpCnsKICBjdXJyZW50LnN0YXRlLmdlbmVzPC1nZW5lLmNsdXN0ZXJzW1t6XV0KICB0YWJsZVMzW3osMV08LWxlbmd0aChjdXJyZW50LnN0YXRlLmdlbmVzKQogIHRhYmxlUzNbeiwyXTwtYmMucmVzaWR1YWwoZ2VuZXMgPSBjdXJyZW50LnN0YXRlLmdlbmVzLGNvbmRpdGlvbnMgPXRpbWUucG9pbnRzLmNsdXN0ZXJzW1t6XV0gLGV4cC5tYXQgPSBtZWRpYW4ubm9ybWFsaXplZC5jb3VudHMgKQogIHRhYmxlUzNbeiwzXTwtY2x1c3Rlci5jb3JyZWxhdGlvbihjdXJyZW50LnN0YXRlLmdlbmVzKQogIHRhYmxlUzNbeiw0XTwtbGVuZ3RoKGJvcnV0YS5zaWduYXR1cmUuZ2VuZXNbW3pdXSkKICB0YWJsZVMzW3osNV08LWxlbmd0aChpbnRlcnNlY3QoYm9ydXRhLnNpZ25hdHVyZS5nZW5lc1tbel1dLGN1cnJlbnQuc3RhdGUuZ2VuZXMpKQogIHRhYmxlUzNbeiw2XTwtcm91bmQodGFibGVTM1t6LDVdL3RhYmxlUzNbeiw0XSxkaWdpdHMgPSAyKQp9CmBgYApQcmludCBUYWJsZSBTMwpgYGB7cn0KcHJpbnQodGFibGVTMykKYGBgCgozYy4gQ29tcGFyZSB0aGUgaWRlbnRpZmllZCB0cmFuc2NyaXB0aW9uYWwgc3RhdGVzIHdpdGggcHJldmlvdXMgaHlwb3hpYSBtb2RlbHMgKFRhYmxlIFMxKQpgYGB7ciBtZXNzYWdlPUZBTFNFfQojSW5pdGlhbCBoeXBveGljIHJlc3BvbnNlCklIUjwtc2NhbigiSUhSX2dlbmVzLmNzdiIsd2hhdD0iY2hhcmFjdGVyIikKI0VuZHVyaW5nIGh5cG94aWMgcmVzcG9uc2UKRUhSPC1zY2FuKCJFSFJfZ2VuZXMuY3N2Iix3aGF0PSJjaGFyYWN0ZXIiKQpOUlA8LXJlYWQuY3N2KCJucnBfZGF0YS5jc3YiLGhlYWRlcj1ULHJvdy5uYW1lcz0xKQojRGVmaW5lIE5vbiByZXBsaWNhdGluZyBwZXJzaXN0ZW5jZSAxIGdlbmVzCk5SUDE8LXJvd25hbWVzKE5SUClbd2hpY2goTlJQJHB2YWwxIDw9IDAuMDUgJiBOUlAkUmF0aW8xID4gMildCiNEZWZpbmUgTm9uIHJlcGxpY2F0aW5nIHBlcnNpc3RlbmNlIDIgZ2VuZXMKTlJQMjwtcm93bmFtZXMoTlJQKVt3aGljaChOUlAkcHZhbDIgPD0gMC4wNSAmIE5SUCRSYXRpbzIgPiAyKV0KI0NvbXBhcmlzb24gbWF0cml4Cm90aGVycy5oeXBveGlhLm1vZGVsczwtYygiSUhSIiwiRUhSIiwiTlJQMSIsIk5SUDIiKQp0YWJsZVMxPC1tYXRyaXgobmNvbD04LG5yb3c9NixOQSxkaW1uYW1lcyA9IGxpc3QodHJhbnNjcmlwdGlvbmFsLnN0YXRlcyxjKCJJSFIgcmVjYWxsIiwiSUhSIFAtdmFsdWUiLCJFSFIgcmVjYWxsIiwiRUhSIFAtdmFsdWUiLCJOUlAxIHJlY2FsbCIsIk5SUDEgUC12YWx1ZSIsIk5SUDIgcmVjYWxsIiwiTlJQMiBQLXZhbHVlIikpKQpmb3IoaSBpbiBuYW1lcyhnZW5lLmNsdXN0ZXJzKSkKewogIGZvcihqIGluIG90aGVycy5oeXBveGlhLm1vZGVscykKICB7CiAgICBvdmVybGFwPC1pbnRlcnNlY3QoZ2VuZS5jbHVzdGVyc1tbaV1dLGV2YWwocGFyc2UodGV4dD1qKSkpCiAgICB0YWJsZVMxW2kscGFzdGUoaiwicmVjYWxsIixzZXA9IiAiKV08LWxlbmd0aChvdmVybGFwKS9sZW5ndGgoZXZhbChwYXJzZSh0ZXh0PWopKSkKICAgIHB2YWw8LXBoeXBlcihsZW5ndGgob3ZlcmxhcCktMSxsZW5ndGgoZXZhbChwYXJzZSh0ZXh0PWopKSksbnJvdyhtZWRpYW4ubm9ybWFsaXplZC5jb3VudHMpLWxlbmd0aChldmFsKHBhcnNlKHRleHQ9aikpKSxsZW5ndGgoZ2VuZS5jbHVzdGVyc1tbaV1dKSxsb3dlci50YWlsPUYpCiAgICB0YWJsZVMxW2kscGFzdGUoaiwiUC12YWx1ZSIsc2VwPSIgIildPC1wdmFsCiAgfQp9CnRhYmxlUzE8LXJvdW5kKHRhYmxlUzEsZGlnaXRzID0gMykKcHJpbnQodGFibGVTMSkKYGBgCjNkLkRlZmluZSB0aGUgc2V0IG9mIGRpZmZlcmVudGlhbHkgZXhwcmVzc2VkIGdlbmVzIGluIEdhbGFnYW4gZXQgYWwuICgyMDEzKSBhbmQgZXZhbHVhdGUgcmVjYWxsIG9mIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBpbiBpbnRyYWNlbGx1bGFyIE1UQiAoY29sbGVjdGVkIHdpdGggUGF0aC1zZXEpIChUYWJsZSBTMikKYGBge3J9CiNMb2FkIEdhbGFnYW4ncyBkYXRhICh0cmlwbGljYXRlcyBmb3IgZWFjaCB0aW1lIHBvaW50KQpnYWxhZ2FuLnJhdy5kYXRhPC1yZWFkLmNzdigiZ2FsYWdhbl9yYXdfaHlwb3hpYV90cy5jc3YiLHJvdy5uYW1lcz0xKQojUGVyZm9ybSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB3aXRoIGJheWVzaWFuIFQtdGVzdCB1c2luZyBDeWJlci1UIChCYWxkaSBhbmQgTG9uZywgMjAwMSkKc291cmNlKCJjeWJlclR0ZXN0LlIiKQojTGlzdCB3aXRoIHRoZSBvdXRwdXQgb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzZXMKZ2FsYWdhbi5kZWE8LWxpc3QoKQojTGlzdCB3aXRoIERFR3MgYXQgZWFjaCB0aW1lIHBvaW50CmdhbGFnYW4uZGVnPC1saXN0KCkKZ2FsYWdhbi5ERUdzLnRvdGFsPC1jKCkKZm9yKHQgaW4gMTo2KQp7CiAgICBnYWxhZ2FuLmRlYVtbdF1dPC1iYXllc1QoYURhdGEgPSBnYWxhZ2FuLnJhdy5kYXRhWyxjKDE6MywoMSsoMyp0KSk6KDMrKDMqdCkpKV0sbnVtQyA9IDMsbnVtRSA9IDMsY29uZiA9IDcsZG9NdWx0dGVzdCA9IFQpCiAgICBnYWxhZ2FuLmRlZ1tbdF1dPC1yb3duYW1lcyhnYWxhZ2FuLmRlYVtbdF1dKVt3aGljaChnYWxhZ2FuLmRlYVtbdF1dJEJIIDwgMC4wNSAmIGFicyhnYWxhZ2FuLmRlYVtbdF1dJG1lYW5DIC0gZ2FsYWdhbi5kZWFbW3RdXSRtZWFuRSk+IDEpXQogICAgZ2FsYWdhbi5ERUdzLnRvdGFsPC11bmlvbihnYWxhZ2FuLkRFR3MudG90YWwsZ2FsYWdhbi5kZWdbW3RdXSkKfQojbG9hZCBsaXN0IG9mIERFR3MgaWRlbnRpZmllZCB3aXRoIFBhdGgtc2VxIChQZXRlcnNvbiBldCBhbCwgMjAxOSkKaW50cmFjZWxsdWxhci5nZW5lczwtc2NhbigiREVHc19pbnRyYWNlbGx1bGFyX01UQl9wYXRoc2VxLnR4dCIsd2hhdD0iY2hhcmFjdGVyIikKdGFibGVTMjwtbWF0cml4KG5yb3c9NCxuY29sPTMsTkEsZGltbmFtZXM9bGlzdChjKCJDb250cm9sbGVkIE8yIiwiR2FsYWdhbjEzIiwiV2F5bmUiLCJEZWZpbmVkIiksYygiT3ZlcmxhcCB3LyBpbnRyYWNlbGx1bGFyIGdlbmVzIiwiUmVjYWxsIiwiUC12YWx1ZSIpKSkKdGFibGVTMlsxLCJPdmVybGFwIHcvIGludHJhY2VsbHVsYXIgZ2VuZXMiXTwtbGVuZ3RoKGludGVyc2VjdChpbnRyYWNlbGx1bGFyLmdlbmVzLERFR3MudGltZWNvdXJzZSkpCnRhYmxlUzJbMSwiUmVjYWxsIl08LXJvdW5kKHRhYmxlUzJbMSwxXS9sZW5ndGgoaW50cmFjZWxsdWxhci5nZW5lcyksZGlnaXRzID0gMykKdGFibGVTMlsxLCJQLXZhbHVlIl08LXBoeXBlcih0YWJsZVMyWzEsMV0tMSxsZW5ndGgoaW50ZXJzZWN0KHJvd25hbWVzKG1lZGlhbi5ub3JtYWxpemVkLmNvdW50cyksaW50cmFjZWxsdWxhci5nZW5lcykpLG5yb3cobWVkaWFuLm5vcm1hbGl6ZWQuY291bnRzKS1sZW5ndGgoaW50ZXJzZWN0KHJvd25hbWVzKG1lZGlhbi5ub3JtYWxpemVkLmNvdW50cyksaW50cmFjZWxsdWxhci5nZW5lcykpLGxlbmd0aChERUdzLnRpbWVjb3Vyc2UpLGxvd2VyLnRhaWwgPSBGKQp0YWJsZVMyWzIsIk92ZXJsYXAgdy8gaW50cmFjZWxsdWxhciBnZW5lcyJdPC1sZW5ndGgoaW50ZXJzZWN0KGludHJhY2VsbHVsYXIuZ2VuZXMsZ2FsYWdhbi5ERUdzLnRvdGFsKSkKdGFibGVTMlsyLCJSZWNhbGwiXTwtcm91bmQodGFibGVTMlsyLDFdL2xlbmd0aChpbnRyYWNlbGx1bGFyLmdlbmVzKSxkaWdpdHMgPSAzKQp0YWJsZVMyWzIsIlAtdmFsdWUiXTwtcGh5cGVyKHRhYmxlUzJbMiwxXS0xLGxlbmd0aChpbnRlcnNlY3Qocm93bmFtZXMoZ2FsYWdhbi5yYXcuZGF0YSksaW50cmFjZWxsdWxhci5nZW5lcykpLG5yb3coZ2FsYWdhbi5yYXcuZGF0YSktbGVuZ3RoKGludGVyc2VjdChyb3duYW1lcyhnYWxhZ2FuLnJhdy5kYXRhKSxpbnRyYWNlbGx1bGFyLmdlbmVzKSksbGVuZ3RoKGdhbGFnYW4uREVHcy50b3RhbCksbG93ZXIudGFpbCA9IEYpCnRhYmxlUzJbMywiT3ZlcmxhcCB3LyBpbnRyYWNlbGx1bGFyIGdlbmVzIl08LWxlbmd0aChpbnRlcnNlY3QoaW50cmFjZWxsdWxhci5nZW5lcyx1bmlvbihOUlAxLE5SUDIpKSkKdGFibGVTMlszLCJSZWNhbGwiXTwtcm91bmQodGFibGVTMlszLDFdL2xlbmd0aChpbnRyYWNlbGx1bGFyLmdlbmVzKSxkaWdpdHMgPSAzKQojVGhlcmUgYXJlIDQsMDk0IGdlbmVzIGluIHRoZSBQYXRoLXNlcSBleHByZXNzaW9uIGRhdGFzZXQsIHNvIHdlIHRha2UgdGhhdCBudW1iZXIgYXMgdGhlIHRvdGFsIHBvcHVsYXRpb24gc2l6ZSBpbiB0aGUgaHlwZXJnZW9tZXRyaWMgdGVzdCAKdGFibGVTMlszLCJQLXZhbHVlIl08LXBoeXBlcih0YWJsZVMyWzMsMV0tMSxsZW5ndGgoaW50cmFjZWxsdWxhci5nZW5lcyksNDA5NC1sZW5ndGgoaW50cmFjZWxsdWxhci5nZW5lcyksbGVuZ3RoKHVuaW9uKE5SUDEsTlJQMikpLGxvd2VyLnRhaWwgPSBGKQp0YWJsZVMyWzQsIk92ZXJsYXAgdy8gaW50cmFjZWxsdWxhciBnZW5lcyJdPC1sZW5ndGgoaW50ZXJzZWN0KGludHJhY2VsbHVsYXIuZ2VuZXMsdW5pb24oSUhSLEVIUikpKQp0YWJsZVMyWzQsIlJlY2FsbCJdPC1yb3VuZCh0YWJsZVMyWzQsMV0vbGVuZ3RoKGludHJhY2VsbHVsYXIuZ2VuZXMpLGRpZ2l0cyA9IDMpCnRhYmxlUzJbNCwiUC12YWx1ZSJdPC1waHlwZXIodGFibGVTMls0LDFdLTEsbGVuZ3RoKGludHJhY2VsbHVsYXIuZ2VuZXMpLDQwOTQtbGVuZ3RoKGludHJhY2VsbHVsYXIuZ2VuZXMpLGxlbmd0aCh1bmlvbihJSFIsRUhSKSksbG93ZXIudGFpbCA9IEYpCnByaW50KHRhYmxlUzIpCmBgYAozZC4gQ3JlYXRlIEZpZyAyQiBkcmFmdApgYGB7cn0KY2x1c3RlcnMuY29sb3JzPC1jKCJibHVlIiwiZGVlcHNreWJsdWUyIiwiZGFya29saXZlZ3JlZW4xIiwiZ3JlZW4zIiwib3JhbmdlIiwicmVkIikKbmFtZXMoY2x1c3RlcnMuY29sb3JzKTwtdHJhbnNjcmlwdGlvbmFsLnN0YXRlcwp0aW1lLnZlY3Rvcjwtc2NhbigidGltZV92ZWN0b3IudHh0IikKdGltZS52ZWN0b3I8LXJvdW5kKHRpbWUudmVjdG9yLGRpZ2l0cyA9IDIpCiNXZSBzdW1tYXJpemVkIGNvbW1vbiB0aGVtZXMgb2Ygc2lnbmlmaWNhbnQgZnVuY3Rpb25hbCB0ZXJtcyBkZWZpbmVkIGJ5IERBVklEIChIdWFuZyBkYSBldCBhbCwgMjAwOSkKREFWSUQuZW5yaWNoZWQuc2VsZWN0ZWQudGVybXM8LWFzLm1hdHJpeChyYmluZChjKDUyLDMxLDE3LDE0LDEwLDEwLDE3LDMwLDM2LDUsMzYsMTQsMjMsMTUsOCw5KSxjKCJUcmFuc2xhdGlvbiIsIk94aWRpZGF0aXZlIHBob3MuIiwiQWEgbWV0LiIsIkFUUCBzeW50aGVzaXMiLCJFVEMiLCJMaXBpZCBtZXQuIiwiQWEgbWV0LiIsIlJlc3BvbnNlIHRvIGh5cG94aWEiLCJUcmFuc2NyaXB0aW9uIHJlZ3VsYXRpb24iLCJIaXN0aWRpbmUgc3ludGhlc2lzIiwKCQkiUFBFIiwiQ29iYWxhbWluIHN5bnRoZXNpcyIsIk1hbW1hbGlhbiBjZWxsIGVudHJ5IiwiRVNBVC02IGxpa2UqIiwiUHJvdGVhc2VzIiwiRE5BIHRyYW5zcG9zaXRpb24iKSxyZXAodHJhbnNjcmlwdGlvbmFsLnN0YXRlc1syOjZdLHRpbWVzPWMoMywzLDQsMywzKSkpKQpyb3duYW1lcyhEQVZJRC5lbnJpY2hlZC5zZWxlY3RlZC50ZXJtcyk8LWMoIk51bWJlciBvZiBnZW5lcyIsIlRlcm0iLCJTdGF0ZSIpCiNTY2FsZSBub3JtYWxpemVkIHJlYWQgY291bnRzIGluIHRoZSBbMCwxXSBpbnRlcnZhbApzY2FsZS5jb3VudHM8LWZ1bmN0aW9uKGNvdW50TWF0cml4KQp7CiAgb3V0cHV0PC1jKCkKZm9yKHEgaW4gcm93bmFtZXMoY291bnRNYXRyaXgpKQp7CiAgbWluLmNvdW50czwtbWluKGNvdW50TWF0cml4W3EsXSkKCW1heC5jb3VudHM8LW1heChjb3VudE1hdHJpeFtxLF0pCgljb3VudHMucmFuZ2U8LW1heC5jb3VudHMgLSBtaW4uY291bnRzCglvdXRwdXQ8LSByYmluZChvdXRwdXQsKGNvdW50TWF0cml4W3EsXS1taW4uY291bnRzKS9jb3VudHMucmFuZ2UpCn0KIHJvd25hbWVzKG91dHB1dCk8LXJvd25hbWVzKGNvdW50TWF0cml4KQogb3V0cHV0Cn0Kc2NhbGVkLm5vcm1hbGl6ZWQubWVkaWFuLmNvdW50czwtc2NhbGUuY291bnRzKG1lZGlhbi5ub3JtYWxpemVkLmNvdW50cykKcm93bmFtZXMoc2NhbGVkLm5vcm1hbGl6ZWQubWVkaWFuLmNvdW50cyk8LXJvd25hbWVzKG1lZGlhbi5ub3JtYWxpemVkLmNvdW50cykKcGFyKG1mcm93PWMoNiwzKSkKcGFyKG1hcj1jKDEsMSwxLDEpKQpmb3IoaiBpbiAxOjYpCnsKICBjdXJyZW50LnN0YXRlPC10cmFuc2NyaXB0aW9uYWwuc3RhdGVzW2pdCiAgbWVhbi5wcm9maWxlPC1jb2xNZWFucyhzY2FsZWQubm9ybWFsaXplZC5tZWRpYW4uY291bnRzW2dlbmUuY2x1c3RlcnNbW2pdXSxdKQogIHBsb3QoeD10aW1lLnZlY3Rvcix5PW1lYW4ucHJvZmlsZSx5bGltPWMoMCwxKSxtYWluPSIiLHhsYWI9IlRpbWUiLHlsYWI9IlNjYWxlZCBjb3VudHMiLHR5cGU9Im8iLHBjaD0xLGNvbD1jbHVzdGVycy5jb2xvcnNbal0sY2V4LmxhYj0xLGNleC5heGlzPTAuNykKICB0ZXh0KGN1cnJlbnQuc3RhdGUseD0yMCx5PTAuMSkKICBmcmFtZSgpCiAgaWYgKGogPT0gMSkKICB7CiAgICBmcmFtZSgpCiAgfQogIGVsc2UKICB7CiAgYmFycGxvdChhcy5udW1lcmljKERBVklELmVucmljaGVkLnNlbGVjdGVkLnRlcm1zWyJOdW1iZXIgb2YgZ2VuZXMiLHdoaWNoKERBVklELmVucmljaGVkLnNlbGVjdGVkLnRlcm1zWyJTdGF0ZSIsXT09Y3VycmVudC5zdGF0ZSldKSxjZXgubGFiPTEsCgluYW1lcz1EQVZJRC5lbnJpY2hlZC5zZWxlY3RlZC50ZXJtc1siVGVybSIsd2hpY2goREFWSUQuZW5yaWNoZWQuc2VsZWN0ZWQudGVybXNbIlN0YXRlIixdPT1jdXJyZW50LnN0YXRlKV0sY29sPXJlcChjbHVzdGVycy5jb2xvcnNbal0sbGVuZ3RoKHdoaWNoKERBVklELmVucmljaGVkLnNlbGVjdGVkLnRlcm1zWyJTdGF0ZSIsXT09Y3VycmVudC5zdGF0ZSkpKSxsYXM9Mixob3JpeiA9IFQseGxpbT1jKDAsNTApKQogfQp9CmBgYAozZS4gQ3JlYXRlIGZpZ3VyZSBzaW1pbGFyIHRvIEZpZyAyYiBidXQgZm9yIGVhY2ggcmVhY3RvcgpgYGB7cn0KI0RlZmluZSBleHByZXNzaW9uIG1hdHJpeCBmb3IgZWFjaCByZXBsaWNhdGUKcmVwQTwtbm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2VbLGdyZXAoIkEiLGNvbG5hbWVzKG5vcm1hbGl6ZWQuY291bnRzLnQxNV9hdmVyYWdlKSldCmNvbG5hbWVzKHJlcEEpPC1wYXN0ZSgiVCIsMDoyNSwiQSIsc2VwPSIiKQpyZXBCPC1ub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZVssZ3JlcCgiQiIsY29sbmFtZXMobm9ybWFsaXplZC5jb3VudHMudDE1X2F2ZXJhZ2UpKV0KY29sbmFtZXMocmVwQik8LXBhc3RlKCJUIiwwOjI1LCJCIixzZXA9IiIpCnJlcEM8LW5vcm1hbGl6ZWQuY291bnRzLnQxNV9hdmVyYWdlWyxncmVwKCJDIixjb2xuYW1lcyhub3JtYWxpemVkLmNvdW50cy50MTVfYXZlcmFnZSkpXQpjb2xuYW1lcyhyZXBDKTwtcGFzdGUoIlQiLDA6MjUsIkMiLHNlcD0iIikKcmVwQS5zY2FsZWQ8LXNjYWxlLmNvdW50cyhyZXBBKQpyZXBCLnNjYWxlZDwtc2NhbGUuY291bnRzKHJlcEIpCnJlcEMuc2NhbGVkPC1zY2FsZS5jb3VudHMocmVwQykKcGFyKG1mcm93PWMoNiwzKSkKcGFyKG1hcj1jKDEsMSwxLDEpKQpmb3IoaiBpbiAxOjYpCnsKICBjdXJyZW50LnN0YXRlPC10cmFuc2NyaXB0aW9uYWwuc3RhdGVzW2pdCiAgbWVhbi5wcm9maWxlLkE8LWNvbE1lYW5zKHJlcEEuc2NhbGVkW2dlbmUuY2x1c3RlcnNbW2pdXSxdKQogIGlmKGo9PTEpCiAgewogIHBsb3QoeD10aW1lLnZlY3Rvcix5PW1lYW4ucHJvZmlsZS5BLHlsaW09YygwLDEpLG1haW49IlJlcGxpY2F0ZSBBIix4bGFiPSJUaW1lIix5bGFiPSJTY2FsZWQgY291bnRzIix0eXBlPSJvIixwY2g9MSxjb2w9Y2x1c3RlcnMuY29sb3JzW2pdLGNleC5sYWI9MSxjZXguYXhpcz0wLjcpCiAgfQogIGVsc2V7CiAgICBwbG90KHg9dGltZS52ZWN0b3IseT1tZWFuLnByb2ZpbGUuQSx5bGltPWMoMCwxKSxtYWluPSIiLHhsYWI9IlRpbWUiLHlsYWI9IlNjYWxlZCBjb3VudHMiLHR5cGU9Im8iLHBjaD0xLGNvbD1jbHVzdGVycy5jb2xvcnNbal0sY2V4LmxhYj0xLGNleC5heGlzPTAuNykKICB9CiAgdGV4dChjdXJyZW50LnN0YXRlLHg9MjAseT0wLjEpCgogIG1lYW4ucHJvZmlsZS5CPC1jb2xNZWFucyhyZXBCLnNjYWxlZFtnZW5lLmNsdXN0ZXJzW1tqXV0sXSkKICBpZihqPT0xKQogIHsKICBwbG90KHg9dGltZS52ZWN0b3IseT1tZWFuLnByb2ZpbGUuQix5bGltPWMoMCwxKSxtYWluPSJSZXBsaWNhdGUgQiIseGxhYj0iVGltZSIseWxhYj0iU2NhbGVkIGNvdW50cyIsdHlwZT0ibyIscGNoPTEsY29sPWNsdXN0ZXJzLmNvbG9yc1tqXSxjZXgubGFiPTEsY2V4LmF4aXM9MC43KQogIH0KICBlbHNlCiAgewogICBwbG90KHg9dGltZS52ZWN0b3IseT1tZWFuLnByb2ZpbGUuQix5bGltPWMoMCwxKSxtYWluPSIiLHhsYWI9IlRpbWUiLHlsYWI9IlNjYWxlZCBjb3VudHMiLHR5cGU9Im8iLHBjaD0xLGNvbD1jbHVzdGVycy5jb2xvcnNbal0sY2V4LmxhYj0xLGNleC5heGlzPTAuNykgCiAgfQogIHRleHQoY3VycmVudC5zdGF0ZSx4PTIwLHk9MC4xKQoKICBtZWFuLnByb2ZpbGUuQzwtY29sTWVhbnMocmVwQy5zY2FsZWRbZ2VuZS5jbHVzdGVyc1tbal1dLF0pCiAgaWYoaj09MSkKICB7CiAgcGxvdCh4PXRpbWUudmVjdG9yLHk9bWVhbi5wcm9maWxlLkMseWxpbT1jKDAsMSksbWFpbj0iUmVwbGljYXRlIEMiLHhsYWI9IlRpbWUiLHlsYWI9IlNjYWxlZCBjb3VudHMiLHR5cGU9Im8iLHBjaD0xLGNvbD1jbHVzdGVycy5jb2xvcnNbal0sY2V4LmxhYj0xLGNleC5heGlzPTAuNykKICB9CiAgZWxzZXsKICAgIHBsb3QoeD10aW1lLnZlY3Rvcix5PW1lYW4ucHJvZmlsZS5DLHlsaW09YygwLDEpLG1haW49IiIseGxhYj0iVGltZSIseWxhYj0iU2NhbGVkIGNvdW50cyIsdHlwZT0ibyIscGNoPTEsY29sPWNsdXN0ZXJzLmNvbG9yc1tqXSxjZXgubGFiPTEsY2V4LmF4aXM9MC43KQogIH0KICB0ZXh0KGN1cnJlbnQuc3RhdGUseD0yMCx5PTAuMSkKfQpgYGAKM2YuIENyZWF0ZSBkcmFmdCBvZiBGaWcgMkMtRgpgYGB7cn0Kc3RhdGVMYWJlbHM9YyhyZXAoMSwyKSxyZXAoMiw2KSw1LDUsMyw1LHJlcCgzLDQpLHJlcCg0LDMpLHJlcCg1LDMpLHJlcCg2LDQpKQojRGF0YSBmcm9tIEthdnZhcyBldCBhbC4gKDIwMTgpCm1ldGFib2xpYy5wYXRod2F5cy50YWI8LXJlYWQuY3N2KCJtZXRhYm9saWNfcGF0aHdheXMuY3N2IixoZWFkZXI9VFJVRSkKbWV0YWJvbGljLmdlbmVzPC11bmlxdWUoc2NhbihmaWxlPSJtdGJfbWV0YWJvbGljX21vZGVsX2dlbmVzLnR4dCIsd2hhdD0iY2hhcmFjdGVycyIpKQojRmlsdGVyIG91dCBub24tY29kaW5nIGVsZW1lbnRzCm1ldGFib2xpYy5nZW5lczwtbWV0YWJvbGljLmdlbmVzW2dyZXAoIlJ2IixtZXRhYm9saWMuZ2VuZXMpXQojQ3JlYXRlIHRhYmxlIHdpdGggdGhlIHBhdGh3YXlzIHJlbGV2YW50IHRvIGVhY2ggZ2VuZQpnZW5lLnBhdGg8LWMoKQpmb3IodCBpbiBtZXRhYm9saWMuZ2VuZXMpCiB7CiBwb3MucGF0aDwtZ3JlcCh0LG1ldGFib2xpYy5wYXRod2F5cy50YWIkR2VuZS5SZWFjdGlvbi5SdWxlKQogcmVsZXZhbnQucGF0aDwtdW5pcXVlKGFzLmNoYXJhY3RlcihtZXRhYm9saWMucGF0aHdheXMudGFiJFN1YnN5c3RlbVtwb3MucGF0aF0pKQogdGVtcC52ZWM8LWNiaW5kKHJlcCh0LGxlbmd0aChyZWxldmFudC5wYXRoKSkscmVsZXZhbnQucGF0aCkKIGdlbmUucGF0aDwtcmJpbmQoZ2VuZS5wYXRoLHRlbXAudmVjKQogfQogYWxsLnBhdGh3YXlzPC11bmlxdWUoZ2VuZS5wYXRoWywyXSkKICNQYXRod2F5cyBzaG93biBpbiBGaWcgMgogc2VsZWN0ZWQucGF0aHdheXM8LWFsbC5wYXRod2F5c1tjKDM1LDQxLDcsNDcpXQogcGFyKG1mcm93PWMoMiwyKSkKIGZvcih6IGluIHNlbGVjdGVkLnBhdGh3YXlzKQogewogZ2VuZXMuaW4ucGF0aDwtaW50ZXJzZWN0KERFR3MudGltZWNvdXJzZSx1bmlxdWUoZ2VuZS5wYXRoW3doaWNoKGdlbmUucGF0aFssMl09PXopLDFdKSkKICNDb21wdXRlIGZvbGQtY2hhbmdlIHJlc3BlY3QgdG8gbWVhbiBub3Jtb3hpYSB0cmFuc2NyaXB0IGxldmVscyAoVDAgJiBUMSkKIGZvbGRjaGFuZ2VzPC1tZWRpYW4ubm9ybWFsaXplZC5jb3VudHNbZ2VuZXMuaW4ucGF0aCwzOjI2XS1yb3dNZWFucyhtZWRpYW4ubm9ybWFsaXplZC5jb3VudHNbZ2VuZXMuaW4ucGF0aCx0aW1lLnBvaW50cy5jbHVzdGVycyROb3Jtb3hpYV0pCiBib3hwbG90KGZvbGRjaGFuZ2VzLG1haW49cGFzdGUoeiwiICgiLGxlbmd0aChnZW5lcy5pbi5wYXRoKSwiKSIsc2VwPSIiKSxuYW1lcz1yb3VuZCh0aW1lLnZlY3RvcixkaWdpdHMgPSAxKVstMSooMToyKV0sbGFzPTIseWxhYj0iTG9nMiBmb2xkLWNoYW5nZSIsb3V0bGluZT1GLGNleC5sYWI9MS41LGNvbD1jbHVzdGVycy5jb2xvcnNbc3RhdGVMYWJlbHNbLTEqYygxOjIpXV0pCiB9CmBgYAozZy4gQ3JlYXRlIEZpZyBTMwpgYGB7cn0KI1RoZSBmaXZlIHR5cGUgc2V2ZW4gc2VjcmV0aW9uIHN5c3RlbXMgKFQ3U1MpIG9mIE1UQgpFU1gxPC1jKCJSdjM4NjgiLCAiUnYzODY5IiwgIlJ2Mzg3NyIsICJSdjM4NzAiLCAiUnYzODcxIiwgIlJ2Mzg4MmMiLCAiUnYzODgzYyIpCkVTWDI8LWMoIlJ2Mzg4NGMiLCAiUnYzODg1YyIsICJSdjM4ODdjIiwgIlJ2Mzg5NGMiLCAiUnYzODk1YyIsICJSdjM4ODljIikKRVNYMzwtYygiUnYwMjgyIiwgIlJ2MDI4MyIsICJSdjAyODQiLCAiUnYwMjkwIiwgIlJ2MDI5MiIsICJSdjAyODkiKQpFU1g0PC1jKCJSdjM0NTBjIiwgIlJ2MzQ0N2MiLCAiUnYzNDQ4IikKRVNYNTwtYygiUnYxNzgyIiwgIlJ2MTc4MyIsICJSdjE3OTUiLCAiUnYxNzk3IiwgIlJ2MTc5OCIpCnBhcihtZnJvdz1jKDIsMikpCmZvcihpIGluIDE6NSkKewogIGdlbmVzPC1ldmFsKHBhcnNlKHRleHQ9cGFzdGUoIkVTWCIsaSxzZXA9IiIpKSkKICBlc3guY29sb3JzPC1yYWluYm93KGxlbmd0aChnZW5lcykpCiAgcGxvdCh4PXRpbWUudmVjdG9yLHk9c2NhbGVkLm5vcm1hbGl6ZWQubWVkaWFuLmNvdW50c1tnZW5lc1sxXSxdLHhsYWI9IlRpbWUgKGgpIixjb2w9ZXN4LmNvbG9yc1sxXSx5bGFiPSJTY2FsZWQgTm9ybWFsaXplZCBjb3VudHMiLHR5cGU9Im8iLG1haW49cGFzdGUoIkVTWCIsaSxzZXA9IiIpLHBjaD0xNix5bGltPWMoMCwxKSkKICBmb3IoZCBpbiAyOmxlbmd0aChnZW5lcykpCiAgewogICAgcG9pbnRzKHg9dGltZS52ZWN0b3IseT1zY2FsZWQubm9ybWFsaXplZC5tZWRpYW4uY291bnRzW2dlbmVzW2RdLF0sdHlwZT0ibyIsY29sPWVzeC5jb2xvcnNbZF0scGNoPTE2KQogIH0KfQpgYGAKNC4gQ3JlYXRlIGhlYXRtYXAgd2l0aCB0cmFuc2NyaXB0aW9uYWwgcHJvZmlsZSBvZiBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgKERFKSBURnMgKEZpZyAzQikKYGBge3J9CiNMb2FkIFRGIG92ZXItZXhwcmVzc2lvbiBkYXRhIChSdXN0YWQgZXQgYWwgMjAxNCkgCm10Yi5leHA8LXJlYWQudGFibGUoInRmb2VfZGF0YS50eHQiLGhlYWRlcj1ULHJvdy5uYW1lcyA9IDEpCmRlLnRmczwtaW50ZXJzZWN0KGNvbG5hbWVzKG10Yi5leHApLERFR3MudGltZWNvdXJzZSkKZGUudGZzLm9yZGVyZWQ8LWMoKQp0ZnMuY29sb3JzPC1yZXAoIk5BIixsZW5ndGgoZGUudGZzKSkKbmFtZXModGZzLmNvbG9ycyk8LWRlLnRmcwpmb3IoaSBpbiB0cmFuc2NyaXB0aW9uYWwuc3RhdGVzKQp7CmRlLnRmcy5jdXJyZW50LnN0YXRlPC1hcy5jaGFyYWN0ZXIoaW50ZXJzZWN0KGRlLnRmcyxnZW5lLmNsdXN0ZXJzW1tpXV0pKQpkZS50ZnMuY3VycmVudC5zdGF0ZTwtZGUudGZzLmN1cnJlbnQuc3RhdGVbb3JkZXIoZGUudGZzLmN1cnJlbnQuc3RhdGUsZGVjcmVhc2luZyA9IEYpXQpkZS50ZnMub3JkZXJlZDwtYyhkZS50ZnMub3JkZXJlZCxkZS50ZnMuY3VycmVudC5zdGF0ZSkKdGZzLmNvbG9yc1tkZS50ZnMuY3VycmVudC5zdGF0ZV08LXJlcChjbHVzdGVycy5jb2xvcnNbaV0sbGVuZ3RoKGRlLnRmcy5jdXJyZW50LnN0YXRlKSkKfQpjb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbCg5LCAiWWxHbkJ1IikpICkoOTkpCmhlYXRtYXAuMihtZWRpYW4ubm9ybWFsaXplZC5jb3VudHNbZGUudGZzLm9yZGVyZWQsXSxSb3d2ID0gIk5BIixkZW5kcm9ncmFtID0gIm5vbmUiLENvbHY9Ik5BIix0cmFjZSA9ICJub25lIixzY2FsZT0icm93Iixjb2wgPSBjb2xvcnMsZGVuc2l0eS5pbmZvPSJub25lIixjZXhSb3c9MC40LG1haW49IkZpZyAzQiIsY29sUm93ID0gdGZzLmNvbG9yc1tkZS50ZnMub3JkZXJlZF0pCmBgYAo1LiBDb21wYXJlIGZvbGQtY2hhbmdlIG9mIERFR3MgaW4gUnYwMDgxIEtPIE1UQiAoaHlwb3hpYSkgdmVyc3VzIHRoZWlyIGZvbGQtY2hhbmdlIGluIHRoZSBSdjAwODEgb3Zlci1leHByZXNzaW5nIE1UQiAobm9ybW94aWEpCmBgYHtyfQojTG9hZCBDaElQLXNlcSBkYXRhIChNaW5jaCBldCBhbC4gMjAxNSkKbXRiLmNoaXA8LXJlYWQuY3N2KCJmaW5hbF9jaGlwc2VxX2JpbmRpbmcuY3N2IixoZWFkZXI9VCkKI0RlZmluZSBSdjAwODEgcmVndWxvbgpSdjAwODEucmVndWxvbjwtbXRiLmNoaXBbd2hpY2gobXRiLmNoaXBbLDFdPT0iUnYwMDgxIiksMl0KI0xvYWQgUnYwMDgxIEtPIGRhdGEgKFN1biBldCBhbC4gMjAxOCkKcnYwMDgxLmRlZy5rbzwtcmVhZC5jc3YoIlJ2MDA4MV9rb19oeXBveGlhLmNzdiIsaGVhZGVyPVQscm93Lm5hbWVzPTEpCiNGaXggbG9jdXMgbmFtZXMgaXNzdWUgKG5vdCBhbGwgb2YgdGhlbSBoYXZlIGEgbG9jdXMgbmFtZSkKbmFtZXMudG8uY2hhbmdlPC1yb3duYW1lcyhydjAwODEuZGVnLmtvKVtncmVwKCJSdiIscm93bmFtZXMocnYwMDgxLmRlZy5rbyksaW52ZXJ0PVQpXQojVXNlIE1pY3JvYmVzT25saW5lIGRhdGEgdG8gY29udmVydCBnZW5lIG5hbWVzIHRvIGxvY2kgbmFtZXMKbXRiLm5hbWVzLnRhYjE8LXJlYWQudGFibGUoIm10Yl9taWNyb2Jlc29ubGluZS50eHQiLGZpbGw9VCxoZWFkZXI9VCkKbXRiLm5hbWVzLnRhYjE8LXVuaXF1ZS5tYXRyaXgobXRiLm5hbWVzLnRhYjEpCm5ldy5uYW1lczwtYygpCmZvcihoIGluIG5hbWVzLnRvLmNoYW5nZSkKewogcG9zLmc8LXdoaWNoKG10Yi5uYW1lcy50YWIxWywyXT09aCkKIGlmKGxlbmd0aChwb3MuZyk+MCkKIHsKICAgbmV3Lm5hbWVzPC1jKG5ldy5uYW1lcyxhcy5jaGFyYWN0ZXIobXRiLm5hbWVzLnRhYjFbcG9zLmcsMV0pKQogfQogZWxzZQogIHsKICAgbmV3Lm5hbWVzPC1jKG5ldy5uYW1lcyxoKQogIH0KfQpyb3duYW1lcyhydjAwODEuZGVnLmtvKVtyb3duYW1lcyhydjAwODEuZGVnLmtvKSAlaW4lIG5hbWVzLnRvLmNoYW5nZV08LW5ldy5uYW1lcwojU2FtZSBpZGVhIHdpdGggaW5mb3JtYXRpb24gZnJvbSB0aGUgTVRCIE5ldHdvcmsgcG9ydGFsCm10Yi5uYW1lcy50YWIyPC1yZWFkLnRhYmxlKCJtdGJfcG9ydGFsX2dlbmVzMjExMS50eHQiLGZpbGw9VCkKbXRiLm5hbWVzLnRhYjI8LXVuaXF1ZS5tYXRyaXgobXRiLm5hbWVzLnRhYjIpCm5hbWVzLnRvLmNoYW5nZTwtcm93bmFtZXMocnYwMDgxLmRlZy5rbylbZ3JlcCgiUnYiLHJvd25hbWVzKHJ2MDA4MS5kZWcua28pLGludmVydD1UKV0KbmV3Lm5hbWVzPC1jKCkKZm9yKGggaW4gbmFtZXMudG8uY2hhbmdlKQp7CiBwb3MuZzwtd2hpY2gobXRiLm5hbWVzLnRhYjJbLDJdPT1oKQogaWYobGVuZ3RoKHBvcy5nKT4wKQogewogICBuZXcubmFtZXM8LWMobmV3Lm5hbWVzLGFzLmNoYXJhY3RlcihtdGIubmFtZXMudGFiMltwb3MuZywxXSkpCiB9CiBlbHNlCiB7CiAgIG5ldy5uYW1lczwtYyhuZXcubmFtZXMsaCkKIH0KfQpyb3duYW1lcyhydjAwODEuZGVnLmtvKVtyb3duYW1lcyhydjAwODEuZGVnLmtvKSAlaW4lIG5hbWVzLnRvLmNoYW5nZV08LW5ldy5uYW1lcwojUnYwMDgxIHRhcmdldHMgdGhhdCB3ZXJlIERFIGluIFJ2MDA4MSBLTwpydjAwODEucmVndWxvbi5kZTwtaW50ZXJzZWN0KHJvd25hbWVzKHJ2MDA4MS5kZWcua28pLFJ2MDA4MS5yZWd1bG9uKQojbXRiLmV4cCBjb250YWlucyB0aGUgZGF0YSBmb3IgUnYwMDgxIG92ZXItZXhwcmVzc2lvbgojRmlyc3QgcGxvdCBSdjAwODEgdGFyZ2V0cyAoYWNjb3JkaW5nIHRvIENoSVAtc2VxIGRhdGEpIHRoYXQgd2VyZSBERSBpbiB0aGUgUnYwMDgxIEtPCnBsb3QocnYwMDgxLmRlZy5rb1tydjAwODEucmVndWxvbi5kZSwiTG9nMi4uX1J2MDA4MS5XVC4iXSxtdGIuZXhwW3J2MDA4MS5yZWd1bG9uLmRlLCJSdjAwODEiXSxtYWluPSJGaWcgUzRBIix4bGFiPSJMb2cyIGZvbGQtY2hhbmdlIFJ2MDA4MSBLTyAoSHlwb3hpYSkiLHlsYWI9IkxvZzIgZm9sZC1jaGFuZ2UgUnYwMDgxIG92ZXItZXhwcmVzc2lvbiAoTm9ybW94aWEpIixjb2w9InJlZCIscGNoPTEseWxpbT1jKC0zLDUuNSkseGxpbT1jKC05LjUsMy41KSkKb3RoZXIuZGUuZ2VuZXMuaW4ucnYwMDgxLmtvPC1pbnRlcnNlY3Qocm93bmFtZXMobXRiLmV4cCksc2V0ZGlmZihyb3duYW1lcyhydjAwODEuZGVnLmtvKSxydjAwODEucmVndWxvbi5kZSkpCiMgQWRkIGdlbmVzIHRoYXQgYXJlIG5vdCBSdjAwODEgdGFyZ2V0cyAoYWNjb3JkaW5nIHRvIENoSVAtc2VxIGRhdGEpCnBvaW50cyhydjAwODEuZGVnLmtvW290aGVyLmRlLmdlbmVzLmluLnJ2MDA4MS5rbywiTG9nMi4uX1J2MDA4MS5XVC4iXSxtdGIuZXhwW290aGVyLmRlLmdlbmVzLmluLnJ2MDA4MS5rbywiUnYwMDgxIl0sY29sPSJkYXJrIGdyZWVuIixwY2g9MSkKYWJsaW5lKGx0eT0yLGNvbD0iZ3JleSIsaD0xKQphYmxpbmUobHR5PTIsY29sPSJncmV5IixoPS0xKQphYmxpbmUobHR5PTIsY29sPSJncmV5Iix2PTEpCmFibGluZShsdHk9Mixjb2w9ImdyZXkiLHY9LTEpCmxlZ2VuZCgidG9wbGVmdCIsbGVnZW5kPWMoIlJ2MDA4MSB0YXJnZXQgKENoSVAtc2VxKSIsIk5vdCBSdjAwODEgdGFyZ2V0IChDaElQLXNlcSkiKSxjb2w9YygicmVkIiwiZGFyayBncmVlbiIpLHB0LmNleD0xLGJ0eT0ibiIsY2V4PTAuNjUscGNoPTEpCmBgYAo2YS4gQ3JlYXRlIEZpZyA0QS1CCmBgYHtyfQogIHRvcC5mZmw8LWxpc3QoYygiUnYwMDgxIiwiUnYzMjQ5YyIpLGMoIlJ2MDA4MSIsIlJ2MDMyNCIpKQogICNSdjAwODEtUnYzMjQ5YyBGRkwKICB0Zi5wYWlyPC10b3AuZmZsW1sxXV0KICBwYXIobWZyb3c9YygxLDMpKQogICNEZXBsZXRpb24tYXNzb2NpYXRlZCBnZW5lcwogIGRlcGxldGlvbi5nZW5lczwtZ2VuZS5jbHVzdGVyc1tbIkRlcGxldGlvbiJdXSAKICBub3QuREVHczwtc2V0ZGlmZihyb3duYW1lcyhtZWRpYW4ubm9ybWFsaXplZC5jb3VudHMpLERFR3MudGltZWNvdXJzZSkKICBtYWluLnRmLnJlZ3Vsb248LW10Yi5jaGlwW3doaWNoKG10Yi5jaGlwWywxXT09dGYucGFpclsxXSksMl0KICBzZWNvbmRhcnkudGYucmVndWxvbjwtbXRiLmNoaXBbd2hpY2gobXRiLmNoaXBbLDFdPT10Zi5wYWlyWzJdKSwyXQogIGZmbC50YXJnZXRzPC1pbnRlcnNlY3QobWFpbi50Zi5yZWd1bG9uLHNlY29uZGFyeS50Zi5yZWd1bG9uKQogIGRlcGxldGlvbi5nZW5lcy5ub3QuYm91bmQuYnkubWFpbi50Zjwtc2V0ZGlmZihkZXBsZXRpb24uZ2VuZXMsbWFpbi50Zi5yZWd1bG9uKQogIGRlcGxldGlvbi5nZW5lcy5ib3VuZC5ieS5tYWluLnRmLm5vdC5zZWNvbmQ8LXNldGRpZmYoaW50ZXJzZWN0KGRlcGxldGlvbi5nZW5lcyxtYWluLnRmLnJlZ3Vsb24pLGZmbC50YXJnZXRzKQogIGRlcGxldGlvbi5nZW5lcy5ib3VuZC5ieS5mZmw8LWludGVyc2VjdChkZXBsZXRpb24uZ2VuZXMsZmZsLnRhcmdldHMpCiAgaGlzdChydjAwODEuZGVnLmtvW2ludGVyc2VjdChkZXBsZXRpb24uZ2VuZXMscm93bmFtZXMocnYwMDgxLmRlZy5rbykpLCJMb2cyLi5fUnYwMDgxLldULiJdLGNvbD0iY2FkZXRibHVlMiIsbWFpbj0iRmlnIDRBIiwKICAgICAgIHhsYWI9IkxvZzIgZm9sZC1jaGFuZ2UgKFJ2MDA4MSBLTyB2cyBXVCkiKQogIGJveHBsb3QobXRiLmV4cFtub3QuREVHcywiUnYwMDgxIl0sbXRiLmV4cFtkZXBsZXRpb24uZ2VuZXMubm90LmJvdW5kLmJ5Lm1haW4udGYsIlJ2MDA4MSJdLG10Yi5leHBbZGVwbGV0aW9uLmdlbmVzLmJvdW5kLmJ5Lm1haW4udGYubm90LnNlY29uZCwiUnYwMDgxIl0sbXRiLmV4cFtkZXBsZXRpb24uZ2VuZXMuYm91bmQuYnkuZmZsLCJSdjAwODEiXSxsYXM9MixuYW1lcz1jKCJBKDE0NjcpIiwiQigzNTIpIiwiQyg2MikiLCJEKDMyKSIpLGNvbD1jKCJncmV5IixyZXAoImNhZGV0Ymx1ZTIiLDMpKSx5bGFiPSJMb2cyIGZvbGQtY2hhbmdlIChSdjAwODEgVEZPRSB2cyBXVCkiLGNleC5heGlzPTEsY2V4LmxhYj0xLG1haW49IkZpZyA0QiIpCnBsb3QubmV3KCkKbGVnZW5kKCJ0b3BsZWZ0IixsZWdlbmQ9YygiQTpub3QgREUgaW4gdGltZSBjb3Vyc2UiLCJCOm5vdCBjb250cm9sbGVkIGJ5IFJ2MDA4MSIsIkM6Y29udHJvbGxlZCBieSBSdjAwODEgYnV0IG5vdCBSdjMyNDljICIsIkQ6Y29udHJvbGxlZCBieSBSdjAwODEtUnYzMjQ5YyBGRkwiKSxjb2w9IndoaXRlIixwdC5jZXg9MSxidHk9Im4iLGNleD0wLjc0LHBjaD0xKQpgYGAKNmIuIENyZWF0ZSBGaWcgNEMtRApgYGB7cn0KICAjUnYwMDgxLVJ2MDMyNAogIHRmLnBhaXI8LXRvcC5mZmxbWzJdXQogICNMYXRlIGh5cG94aWEtYXNzb2NpYXRlZCBnZW5lcwogIGxhdGUuZ2VuZXM8LWdlbmUuY2x1c3RlcnNbWyJMYXRlIl1dIAogIG1haW4udGYucmVndWxvbjwtbXRiLmNoaXBbd2hpY2gobXRiLmNoaXBbLDFdPT10Zi5wYWlyWzFdKSwyXQogIHNlY29uZGFyeS50Zi5yZWd1bG9uPC1tdGIuY2hpcFt3aGljaChtdGIuY2hpcFssMV09PXRmLnBhaXJbMl0pLDJdCiAgZmZsLnRhcmdldHM8LWludGVyc2VjdChtYWluLnRmLnJlZ3Vsb24sc2Vjb25kYXJ5LnRmLnJlZ3Vsb24pCiAgbGF0ZS5nZW5lcy5ub3QuYm91bmQuYnkubWFpbi50Zjwtc2V0ZGlmZihsYXRlLmdlbmVzLG1haW4udGYucmVndWxvbikKICBsYXRlLmdlbmVzLmJvdW5kLmJ5Lm1haW4udGYubm90LnNlY29uZDwtc2V0ZGlmZihpbnRlcnNlY3QobGF0ZS5nZW5lcyxtYWluLnRmLnJlZ3Vsb24pLGZmbC50YXJnZXRzKQogIGxhdGUuZ2VuZXMuYm91bmQuYnkuZmZsPC1pbnRlcnNlY3QobGF0ZS5nZW5lcyxmZmwudGFyZ2V0cykKICBwYXIobWZyb3c9YygxLDMpKQogIGhpc3QocnYwMDgxLmRlZy5rb1tpbnRlcnNlY3QobGF0ZS5nZW5lcyxyb3duYW1lcyhydjAwODEuZGVnLmtvKSksIkxvZzIuLl9SdjAwODEuV1QuIl0sY29sPSJwZWFjaHB1ZmYiLG1haW49IkZpZyA0QyIsCiAgICAgICB4bGFiPSJMb2cyIGZvbGQtY2hhbmdlIChSdjAwODEgS08gdnMgV1QpIikKICBib3hwbG90KG10Yi5leHBbbm90LkRFR3MsIlJ2MDA4MSJdLG10Yi5leHBbbGF0ZS5nZW5lcy5ub3QuYm91bmQuYnkubWFpbi50ZiwiUnYwMDgxIl0sbXRiLmV4cFtsYXRlLmdlbmVzLmJvdW5kLmJ5Lm1haW4udGYubm90LnNlY29uZCwiUnYwMDgxIl0sbXRiLmV4cFtsYXRlLmdlbmVzLmJvdW5kLmJ5LmZmbCwiUnYwMDgxIl0sbmFtZXM9YygiQSgxNDY3KSIsIkIoODE4KSIsIkMoOTkpIiwiRCg2MSkiKSxsYXM9Mixjb2w9YygiZ3JleSIscmVwKCJwZWFjaHB1ZmYiLDMpKSx5bGFiPSJMb2cyIGZvbGQtY2hhbmdlIChSdjAwODEgVEZPRSB2cyBXVCkiLGNleC5heGlzPTEsY2V4LmxhYj0xLG1haW49IkZpZyA0RCIpCnBsb3QubmV3KCkKbGVnZW5kKCJ0b3BsZWZ0IixsZWdlbmQ9YygiQTpub3QgREUgaW4gdGltZSBjb3Vyc2UiLCJCOm5vdCBjb250cm9sbGVkIGJ5IFJ2MDA4MSIsIkM6Y29udHJvbGxlZCBieSBSdjAwODEgYnV0IG5vdCBSdjAzMjQgIiwiRDpjb250cm9sbGVkIGJ5IFJ2MDA4MS1SdjAzMjQgRkZMIiksY29sPSJ3aGl0ZSIscHQuY2V4PTEsYnR5PSJuIixjZXg9MC43NCxwY2g9MSkKYGBgCjcuIEdlbmVyYXRlIGlucHV0IGZpbGVzIGZvciBNb3RpZk5ldCBzZXJ2ZXIgKEZGTCBkZXRlY3Rpb24pCiNNb3RpZk5ldCBydW4gaXMgdGVtcG9yYXJpbHkgYXZhaWxhYmxlIGluOiBodHRwOi8vbmV0YmlvLmJndS5hYy5pbC9tb3RpZm5ldC8jL2V4cGxvcmVyLzE0MTQKYGBge3J9CiNUaGUgaW5wdXQgbmV0d29yayBpcyByZXN0cmljdGVkIHRvIHRoZSBpbnRlcmFjdGlvbnMgdGhhdCBpbnZvbHZlIERFIFRGcyBhbmQgdGhlaXIgdGFyZ2V0cwppbnB1dC5uZXR3b3JrPC1jKCkKI0ZpbHRlciB0aGUgTVRCIENoSVAtc2VxIGRlcml2ZWQgbmV0d29yayB1c2VkIGluIEl0ZW0gIyA1CmZvcihrIGluIDE6bnJvdyhtdGIuY2hpcCkpCnsKICBjdXJyZW50LnRmPC1tdGIuY2hpcFtrLDFdCiAgY3VycmVudC50YXJnZXQ8LW10Yi5jaGlwW2ssMl0KICBpZihjdXJyZW50LnRmICVpbiUgREVHcy50aW1lY291cnNlICYgY3VycmVudC50YXJnZXQgJWluJSBERUdzLnRpbWVjb3Vyc2UgKQogIHsKICAgIGlucHV0Lm5ldHdvcms8LXJiaW5kKGlucHV0Lm5ldHdvcmssbXRiLmNoaXBbayxdKQogIH0KfQppbnB1dC5ub2RlczwtdW5pb24oaW5wdXQubmV0d29ya1ssMV0saW5wdXQubmV0d29ya1ssMl0pCiNXcml0ZS5maWxlcyAob3B0aW9uYWwpCiN3cml0ZS50YWJsZShmaWxlPSJpbnB1dF9uZXR3b3JrLnR4dCIsaW5wdXQubmV0d29yayxxdW90ZSA9IEYscm93Lm5hbWVzID0gRixzZXAgPSAiXHQiKQojd3JpdGUudGFibGUoZmlsZT0iaW5wdXRfbm9kZXMudHh0IixpbnB1dC5ub2RlcyxxdW90ZSA9IEYscm93Lm5hbWVzID0gRikKYGBgCjhhLiBFdmFsdWF0ZSBvdmVybGFwIGJldHdlZW4gVEYgcmVndWxvbnMgYW5kIHRoZSBpZGVudGlmaWVkIHRyYW5zY3JpcHRpb25hbCBzdGF0ZXMgLSBQYXJ0IEkKYGBge3J9CnJlZ3Vsb25zLmVucmljaGVkLndpdGgub3duLnN0YXRlPC1jKCkKZm9yKHogaW4gZGUudGZzKQp7CiAgY3VycmVudC5yZWd1bG9uPC1pbnRlcnNlY3QobXRiLmNoaXBbd2hpY2gobXRiLmNoaXBbLDFdPT16KSwyXSxERUdzLnRpbWVjb3Vyc2UpCiAgI0RlZmluZSB0aGUgc3RhdGUgb2YgZWFjaCBERSBURgogICNEZWZhdWx0IHZhbHVlIGlzIE5vcm1veGlhCiAgdGYuc3RhdGU8LTEKICBmb3IoeCBpbiAyOjYpCiAgewogIGlmKGxlbmd0aChncmVwKHosZ2VuZS5jbHVzdGVyc1tbeF1dKSk+MCkKICB7CiAgICB0Zi5zdGF0ZTwteAogIH0KICB9CiAgbWVtYmVycy5jdXJyZW50LnJlZ3Vsb24uaW4uVEYuc3RhdGU8LWludGVyc2VjdChjdXJyZW50LnJlZ3Vsb24sZ2VuZS5jbHVzdGVyc1tbdGYuc3RhdGVdXSkKICByZWd1bG9ucy5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZS5wdmFsdWU8LXBoeXBlcihsZW5ndGgobWVtYmVycy5jdXJyZW50LnJlZ3Vsb24uaW4uVEYuc3RhdGUpLTEsbGVuZ3RoKGdlbmUuY2x1c3RlcnNbW3RmLnN0YXRlXV0pLGxlbmd0aChERUdzLnRpbWVjb3Vyc2UpLWxlbmd0aChnZW5lLmNsdXN0ZXJzW1t0Zi5zdGF0ZV1dKSxsZW5ndGgoY3VycmVudC5yZWd1bG9uKSxsb3dlci50YWlsID0gRikKICAgIGlmKHJlZ3Vsb25zLmVucmljaGVkLndpdGgub3duLnN0YXRlLnB2YWx1ZSA8PSAwLjA1KQogICAgewogICAgICByZWd1bG9ucy5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZTwtYyhyZWd1bG9ucy5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZSx6KQogICAgfQp9CnByaW50KHBhc3RlKCJUaGVyZSBhcmUiLGxlbmd0aChyZWd1bG9ucy5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZSksICJyZWd1bG9ucyBlbnJpY2hlZCB3aXRoIGdlbmVzIGFzc2lnbmVkIHRvIHRoZSBzYW1lIHRyYW5zY3JpcHRpb25hbCBzdGF0ZSBhcyB0aGUgcmVndWxhdGluZyBURiIsc2VwPSIgIikpCiNQZXJtdXRhdGlvbiB0ZXN0IHRvIGV2YWx1YXRlIHNpZ25pZmljYW5jZSBvZiBlbnJpY2htZW50IGluc3RhbmNlcwojTnVtYmVyIG9mIHBlcm11dGF0aW9ucwpOPTEwMDAKY291bnQucmVndWxvbnMucmFuZG9tbHkuZW5yaWNoZWQud2l0aC5zYW1lLnN0YXRlPC1jKCkKZm9yKHcgaW4gMTpOKQp7CiNTaHVmZmxlZCBERUdzIHN0YXRlLW1lbWJlcnNoaXAKc2h1ZmZsZWQuZGVnczwtREVHcy50aW1lY291cnNlW3NhbXBsZSgxOmxlbmd0aChERUdzLnRpbWVjb3Vyc2UpLGxlbmd0aChERUdzLnRpbWVjb3Vyc2UpKV0KcmFuZG9tLnN0YXRlczwtbGlzdCgpCmZvcihpIGluIDE6NikKewogIHJhbmRvbS5zdGF0ZXNbW2ldXTwtc2h1ZmZsZWQuZGVnc1sxOmxlbmd0aChnZW5lLmNsdXN0ZXJzW1tpXV0pXQogIHNodWZmbGVkLmRlZ3M8LXNodWZmbGVkLmRlZ3NbLTEqKDE6bGVuZ3RoKHJhbmRvbS5zdGF0ZXNbW2ldXSkpXQp9CiNFdmFsdWF0ZSByYW5kb20gZW5yaWNobWVudApyZWd1bG9ucy5yYW5kb21seS5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZTwtYygpCmZvcih6IGluIGRlLnRmcykKewogIGN1cnJlbnQucmVndWxvbjwtaW50ZXJzZWN0KG10Yi5jaGlwW3doaWNoKG10Yi5jaGlwWywxXT09eiksMl0sREVHcy50aW1lY291cnNlKQogICNEZWZpbmUgdGhlIHN0YXRlIG9mIGVhY2ggREUgVEYKICAjRGVmYXVsdCB2YWx1ZSBpcyBOb3Jtb3hpYQogIHRmLnN0YXRlPC0xCiAgZm9yKHggaW4gMjo2KQogIHsKICBpZihsZW5ndGgoZ3JlcCh6LHJhbmRvbS5zdGF0ZXNbW3hdXSkpPjApCiAgewogICAgdGYuc3RhdGU8LXgKICB9CiAgfQogIG1lbWJlcnMuY3VycmVudC5yZWd1bG9uLmluLnJhbmRvbS5URi5zdGF0ZTwtaW50ZXJzZWN0KGN1cnJlbnQucmVndWxvbixyYW5kb20uc3RhdGVzW1t0Zi5zdGF0ZV1dKQogIHJlZ3Vsb25zLnJhbmRvbWx5LmVucmljaGVkLndpdGgub3duLnN0YXRlLnB2YWx1ZTwtcGh5cGVyKGxlbmd0aChtZW1iZXJzLmN1cnJlbnQucmVndWxvbi5pbi5yYW5kb20uVEYuc3RhdGUpLTEsbGVuZ3RoKHJhbmRvbS5zdGF0ZXNbW3RmLnN0YXRlXV0pLGxlbmd0aChERUdzLnRpbWVjb3Vyc2UpLWxlbmd0aChyYW5kb20uc3RhdGVzW1t0Zi5zdGF0ZV1dKSxsZW5ndGgoY3VycmVudC5yZWd1bG9uKSxsb3dlci50YWlsID0gRikKICAgIGlmKHJlZ3Vsb25zLnJhbmRvbWx5LmVucmljaGVkLndpdGgub3duLnN0YXRlLnB2YWx1ZSA8PSAwLjA1KQogICAgewogICAgICByZWd1bG9ucy5yYW5kb21seS5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZTwtYyhyZWd1bG9ucy5yYW5kb21seS5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZSx6KQogICAgfQp9CmNvdW50LnJlZ3Vsb25zLnJhbmRvbWx5LmVucmljaGVkLndpdGguc2FtZS5zdGF0ZTwtYyhjb3VudC5yZWd1bG9ucy5yYW5kb21seS5lbnJpY2hlZC53aXRoLnNhbWUuc3RhdGUsbGVuZ3RoKHJlZ3Vsb25zLnJhbmRvbWx5LmVucmljaGVkLndpdGgub3duLnN0YXRlKSkKfQojUGVybXV0YXRpb24gcC12YWx1ZQpwcmludChsZW5ndGgod2hpY2goY291bnQucmVndWxvbnMucmFuZG9tbHkuZW5yaWNoZWQud2l0aC5zYW1lLnN0YXRlID49IGxlbmd0aChyZWd1bG9ucy5lbnJpY2hlZC53aXRoLm93bi5zdGF0ZSkpKSkKYGBgCjhiLiBFdmFsdWF0ZSBvdmVybGFwIGJldHdlZW4gVEYgcmVndWxvbnMgYW5kIHRoZSBpZGVudGlmaWVkIHRyYW5zY3JpcHRpb25hbCBzdGF0ZXMgLSBQYXJ0IElJCmBgYHtyfQpyZWd1bG9ucy5lbnJpY2hlZC53aXRoLm90aGVyLnN0YXRlPC1jKCkKZm9yKHogaW4gZGUudGZzKQp7CiAgY3VycmVudC5yZWd1bG9uPC1pbnRlcnNlY3QobXRiLmNoaXBbd2hpY2gobXRiLmNoaXBbLDFdPT16KSwyXSxERUdzLnRpbWVjb3Vyc2UpCiAgI0RlZmluZSB0aGUgc3RhdGUgb2YgZWFjaCBERSBURgogICNEZWZhdWx0IHZhbHVlIGlzIE5vcm1veGlhCiAgdGYuc3RhdGU8LTEKICBmb3IoeCBpbiAyOjYpCiAgewogIGlmKGxlbmd0aChncmVwKHosZ2VuZS5jbHVzdGVyc1tbeF1dKSk+MCkKICB7CiAgICB0Zi5zdGF0ZTwteAogIH0KICB9CiAgZm9yKHEgaW4gc2V0ZGlmZigxOjYsdGYuc3RhdGUpKQogIHsKICAgbWVtYmVycy5jdXJyZW50LnJlZ3Vsb24uaW4ub3RoZXIuc3RhdGU8LWludGVyc2VjdChjdXJyZW50LnJlZ3Vsb24sZ2VuZS5jbHVzdGVyc1tbcV1dKQogIGVucmljaG1lbnQud2l0aC5vdGhlci5zdGF0ZS5wdmFsdWU8LXBoeXBlcihsZW5ndGgobWVtYmVycy5jdXJyZW50LnJlZ3Vsb24uaW4ub3RoZXIuc3RhdGUpLTEsbGVuZ3RoKGdlbmUuY2x1c3RlcnNbW3FdXSksbGVuZ3RoKERFR3MudGltZWNvdXJzZSktbGVuZ3RoKGdlbmUuY2x1c3RlcnNbW3FdXSksbGVuZ3RoKGN1cnJlbnQucmVndWxvbiksbG93ZXIudGFpbCA9IEYpCiAgICBpZihlbnJpY2htZW50LndpdGgub3RoZXIuc3RhdGUucHZhbHVlIDw9IDAuMDUpCiAgICB7CiAgICAgIHJlZ3Vsb25zLmVucmljaGVkLndpdGgub3RoZXIuc3RhdGU8LWMocmVndWxvbnMuZW5yaWNoZWQud2l0aC5vdGhlci5zdGF0ZSx6KQogICAgfQogIH0KfQpwcmludChwYXN0ZSgiVGhlcmUgYXJlIixsZW5ndGgodW5pcXVlKHJlZ3Vsb25zLmVucmljaGVkLndpdGgub3RoZXIuc3RhdGUpKSwgInJlZ3Vsb25zIGVucmljaGVkIHdpdGggZ2VuZXMgYXNzaWduZWQgYSB0cmFuc2NyaXB0aW9uYWwgc3RhdGUgZGlmZmVyZW50IGZyb20gdGhlIG9uZSBvZiB0aGUgcmVndWxhdGluZyBURiIsc2VwPSIgIikpCmBgYA==